posimai-root/docs/synology/SYNOLOGY_SAVE_FLOW_DIAGRAM.md

26 KiB
Raw Blame History

Synology 保存機能 — 構成とフロー図解

全体構成図

┌─────────────────────────────────────────────────────────────────────┐
│                         Posimai エコシステム                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                       │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐  ┌─────────────┐  │
│  │  Reader    │  │   Feed     │  │   Brain    │  │    Maps     │  │
│  │  (Vercel)  │  │  (Vercel)  │  │  (Vercel)  │  │  (Vercel)   │  │
│  │            │  │            │  │            │  │             │  │
│  │ 記事閲覧   │  │ RSS収集    │  │ 一覧表示   │  │ 位置情報    │  │
│  └─────┬──────┘  └─────┬──────┘  └─────┬──────┘  └─────────────┘  │
│        │                │                │                           │
│        │  「Brainに保存」ボタン          │                           │
│        └────────────────┴────────────────┘                           │
│                         │                                            │
│                         │ POST /brain/api/save                       │
│                         │ { url, title, content, source }            │
│                         ▼                                            │
│        ┌────────────────────────────────────────────┐               │
│        │     Synology NAS (Home Server)             │               │
│        │  Tailscale: posimai-lab.tail72e846.ts.net  │               │
│        ├────────────────────────────────────────────┤               │
│        │  ┌──────────────────────────────────────┐ │               │
│        │  │  Brain API (Node.js + Express)       │ │               │
│        │  │  Port: 8090                           │ │               │
│        │  │  Container: posimai_api               │ │               │
│        │  │                                       │ │               │
│        │  │  1. 認証JWT/APIキー               │ │               │
│        │  │  2. 本文処理content || OGP取得    │ │               │
│        │  │  3. Gemini AI 分析 ──────────────┐   │ │               │
│        │  │  4. PostgreSQL保存                │   │ │               │
│        │  └───────────────────────────┬────────┘   │ │               │
│        │                              │            │ │               │
│        │  ┌───────────────────────────▼──────────┐ │               │
│        │  │  PostgreSQL Database                 │ │               │
│        │  │  Port: 5432                          │ │               │
│        │  │  Container: (別コンテナまたはSQLite) │ │               │
│        │  │                                      │ │               │
│        │  │  Table: articles                     │ │               │
│        │  │  ・id, user_id, url, title           │ │               │
│        │  │  ・full_text (本文全文) ← Phase 1    │ │               │
│        │  │  ・summary (AI要約)                  │ │               │
│        │  │  ・topics (AIトピック抽出)           │ │               │
│        │  │  ・reading_time (読了時間)           │ │               │
│        │  │  ・favicon, og_image                 │ │               │
│        │  │  ・status (inbox/favorite/archived)  │ │               │
│        │  └──────────────────────────────────────┘ │               │
│        │                                            │               │
│        │  ┌──────────────────────────────────────┐ │               │
│        │  │  Gemini API (External)               │ │               │
│        │  │  Model: gemini-2.0-flash-exp         │ │               │
│        │  │                                      │ │               │
│        │  │  Input: title + full_text (5000文字)│ │               │
│        │  │  Output:                             │ │               │
│        │  │   ・summary (3文の要約)              │ │               │
│        │  │   ・topics (2つのテーマ)             │ │               │
│        │  │   ・readingTime (推定分数)           │ │               │
│        │  └──────────────────────────────────────┘ │               │
│        └────────────────────────────────────────────┘               │
│                                                                       │
└─────────────────────────────────────────────────────────────────────┘

保存フロー詳細(シーケンス図)

1. Reader/Feed → Brain 保存フロー

┌─────────┐        ┌──────────────┐        ┌──────────────┐        ┌──────────┐
│ Reader  │        │  Synology    │        │  PostgreSQL  │        │ Gemini   │
│ (Vercel)│        │  Brain API   │        │   Database   │        │   API    │
└────┬────┘        └──────┬───────┘        └──────┬───────┘        └────┬─────┘
     │                    │                       │                     │
     │  1. ユーザーが      │                       │                     │
     │  「Brainに保存」   │                       │                     │
     │  ボタンをクリック   │                       │                     │
     │                    │                       │                     │
     │  2. POST /save     │                       │                     │
     │  {                 │                       │                     │
     │    url: "...",     │                       │                     │
     │    title: "...",   │                       │                     │
     │    content: "...", │ (本文全文)            │                     │
     │    source: "reader"│                       │                     │
     │  }                 │                       │                     │
     ├───────────────────>│                       │                     │
     │                    │                       │                     │
     │                    │  3. 認証チェック      │                     │
     │                    │  (JWT/APIキー)        │                     │
     │                    │                       │                     │
     │                    │  4. 本文取得          │                     │
     │                    │  ・content あり       │                     │
     │                    │    → そのまま使用     │                     │
     │                    │  ・content なし       │                     │
     │                    │    → fetchOGP()       │                     │
     │                    │      (OGP description │                     │
     │                    │       を代替使用)     │                     │
     │                    │                       │                     │
     │                    │  5. AI分析リクエスト  │                     │
     │                    │  analyzeWithGemini(   │                     │
     │                    │    title,             │                     │
     │                    │    fullText,          │                     │
     │                    │    url                │                     │
     │                    │  )                    │                     │
     │                    ├───────────────────────────────────────────>│
     │                    │                       │                     │
     │                    │                       │  6. AI処理         │
     │                    │                       │  ・要約生成         │
     │                    │                       │  ・トピック抽出     │
     │                    │                       │  ・読了時間推定     │
     │                    │                       │  (5000文字制限)    │
     │                    │                       │                     │
     │                    │  7. AI分析結果        │                     │
     │                    │  {                    │                     │
     │                    │    summary: "...",    │                     │
     │                    │    topics: [...],     │                     │
     │                    │    readingTime: 5     │                     │
     │                    │  }                    │                     │
     │                    │<──────────────────────────────────────────┤
     │                    │                       │                     │
     │                    │  8. DB保存            │                     │
     │                    │  INSERT INTO articles │                     │
     │                    │  (user_id, url,       │                     │
     │                    │   title, full_text,   │ ← Phase 1 新規     │
     │                    │   summary, topics,    │                     │
     │                    │   source, ...)        │                     │
     │                    │  ON CONFLICT UPDATE   │ (重複時は更新)     │
     │                    ├──────────────────────>│                     │
     │                    │                       │                     │
     │                    │  9. 保存完了          │                     │
     │                    │<──────────────────────┤                     │
     │                    │                       │                     │
     │  10. レスポンス     │                       │                     │
     │  {                 │                       │                     │
     │    success: true,  │                       │                     │
     │    articleId: 123, │                       │                     │
     │    fullTextSaved:  │                       │                     │
     │      true,         │                       │                     │
     │    textLength: 2500│                       │                     │
     │  }                 │                       │                     │
     │<───────────────────┤                       │                     │
     │                    │                       │                     │
     │  11. UI フィードバック                     │                     │
     │  ・ボタンを緑色に   │                       │                     │
     │  ・「保存済み」表示 │                       │                     │
     │                    │                       │                     │

2. Brain 一覧表示フロー

┌─────────┐        ┌──────────────┐        ┌──────────────┐
│  Brain  │        │  Synology    │        │  PostgreSQL  │
│ (Vercel)│        │  Brain API   │        │   Database   │
└────┬────┘        └──────┬───────┘        └──────┬───────┘
     │                    │                       │
     │  1. ページを開く    │                       │
     │  (初回ロード)       │                       │
     │                    │                       │
     │  2. GET /articles  │                       │
     │  Authorization:    │                       │
     │    Bearer pk_xxx   │                       │
     ├───────────────────>│                       │
     │                    │                       │
     │                    │  3. 認証チェック      │
     │                    │                       │
     │                    │  4. 記事一覧取得      │
     │                    │  SELECT id, url,      │
     │                    │    title, full_text,  │ ← Phase 1 追加
     │                    │    summary, topics,   │
     │                    │    ...                │
     │                    │  FROM articles        │
     │                    │  WHERE user_id = $1   │
     │                    │  ORDER BY             │
     │                    │    created_at DESC    │
     │                    ├──────────────────────>│
     │                    │                       │
     │                    │  5. 記事データ        │
     │                    │<──────────────────────┤
     │                    │                       │
     │  6. JSON レスポンス│                       │
     │  {                 │                       │
     │    articles: [     │                       │
     │      {             │                       │
     │        id: 123,    │                       │
     │        url: "...", │                       │
     │        title: "...│                       │
     │        fullText:   │ ← Phase 1 新規       │
     │          "...",    │   (本文表示用)       │
     │        summary:    │                       │
     │          "...",    │                       │
     │        topics: [...│                       │
     │      }             │                       │
     │    ]               │                       │
     │  }                 │                       │
     │<───────────────────┤                       │
     │                    │                       │
     │  7. UI レンダリング│                       │
     │  ・カード一覧表示   │                       │
     │  ・クリックで詳細   │                       │
     │    モーダル表示     │                       │
     │                    │                       │

3. Brain 本文表示フロー(詳細モーダル)

┌─────────┐        ┌──────────────┐
│  Brain  │        │  Memory      │
│  UI     │        │  Cache       │
└────┬────┘        └──────┬───────┘
     │                    │
     │  1. カードをクリック│
     │  openArticleDetail │
     │    Modal(article)  │
     │                    │
     │  2. キャッシュ確認  │
     │  fullTextCache     │
     │    .has(article.id)│
     ├───────────────────>│
     │                    │
     │  3a. キャッシュHIT │
     │  (2回目以降)        │
     │<───────────────────┤
     │  fullText を即表示  │
     │  (APIコールなし)    │
     │                    │
     │  3b. キャッシュMISS│
     │  (初回)             │
     │                    │
     │  4. article.fullText│
     │  がすでに含まれて   │
     │  いる場合           │
     │  → 直接表示         │
     │  (Phase 1で対応済み)│
     │                    │
     │  5. Markdown変換    │
     │  renderMarkdown(   │
     │    fullText         │
     │  )                 │
     │  ・# → <h1>         │
     │  ・**text** →       │
     │    <strong>         │
     │  ・[link](url) →    │
     │    <a href>         │
     │                    │
     │  6. XSS対策         │
     │  DOMPurify         │
     │    .sanitize()     │
     │                    │
     │  7. 表示            │
     │  モーダル内に       │
     │  HTML レンダリング  │
     │  (CSS: .article-   │
     │   detail-md)        │
     │                    │
     │  8. キャッシュ保存  │
     │  fullTextCache     │
     │    .set(id, text)  │
     ├───────────────────>│
     │                    │

データの流れPhase 1: 本文全文保存)

Before Phase 1従来

Reader/Feed
   │
   ├─ URL + Title のみ送信
   │
   ▼
Synology API
   │
   ├─ fetchOGP() で OGP description 取得(短い)
   │
   ├─ Gemini 分析(短い説明のみ)
   │    → 要約の質が低い
   │
   ▼
PostgreSQL
   │
   └─ summary (AI要約、精度低)
   └─ full_text: NULL  ← 本文なし!

Brain UI
   │
   └─ 「本文を読む」ボタンなし
   └─ AI要約のみ表示精度低

After Phase 1現在

Reader/Feed
   │
   ├─ URL + Title + Content (本文全文) を送信
   │
   ▼
Synology API
   │
   ├─ content あり → そのまま使用
   │   content なし → fetchOGP() で代替
   │
   ├─ Gemini 分析本文全文から5000文字
   │    → 高精度な要約・トピック抽出
   │
   ▼
PostgreSQL
   │
   ├─ full_text: "記事の本文全文..."  ← Phase 1 新規!
   ├─ summary: "高精度AI要約"
   └─ topics: ["技術", "Web開発"]

Brain UI
   │
   ├─ 一覧: AI要約を表示
   │
   └─ クリック → モーダル
       │
       ├─ 「本文を読む」タブ
       │   → full_text を Markdown 表示
       │
       └─ メモリキャッシュ2回目以降は即表示

技術スタック

レイヤー 技術 用途
Frontend HTML/CSS/JS (Vanilla) Brain UI (Vercel)
API Server Node.js + Express Synology NAS (Docker)
Database PostgreSQL Synology NAS (Docker)
AI Gemini 2.0 Flash Exp 要約・トピック抽出
Network Tailscale Synology へのセキュアアクセス
Hosting Vercel (Frontend) + Synology (Backend) ハイブリッド構成

Phase 1 の主な変更点

1. データベース

Migration: synology-brain-migration.sql

-- 本文全文カラムを追加
ALTER TABLE articles ADD COLUMN IF NOT EXISTS full_text TEXT;

-- 画像用(将来用)
ALTER TABLE articles ADD COLUMN IF NOT EXISTS images TEXT[];

-- 更新日時用
ALTER TABLE articles ADD COLUMN IF NOT EXISTS updated_at TIMESTAMP DEFAULT NOW();

2. API エンドポイント

File: synology-brain-api-save-endpoint.js

POST /save の変更点

  • Before: { url, title, source } のみ
  • After: { url, title, content, source }content 追加
// Before: OGP description のみ
const meta = await fetchOGP(url);
const ai = await analyzeWithGemini(title, meta.desc, url);
//                                         ^^^^^^^^ 短い

// After: 本文全文を優先
let fullText = content || null;
if (!fullText) {
    meta = await fetchOGP(url);
    fullText = meta.desc || '';  // フォールバック
}
const ai = await analyzeWithGemini(title, fullText, url);
//                                         ^^^^^^^^^ 長い!

GET /articles の変更点

  • Before: full_text カラムを返さない
  • After: full_text を含めて返す
// Before
SELECT id, url, title, summary, topics, ...
FROM articles

// After
SELECT id, url, title, full_text, summary, topics, ...
//                      ^^^^^^^^^ 追加
FROM articles

3. Brain UI (Vercel)

File: posimai-brain/js/ui/actions.js

Markdown レンダリング

// 1. Markdown → HTML 変換
function renderMarkdown(md) {
    // # → <h1>, **text** → <strong>, [link](url) → <a>
    // ...
}

// 2. XSS 対策DOMPurify
DOMPurify.sanitize(html, {
    ALLOWED_TAGS: ['p', 'h1', 'h2', 'h3', 'strong', 'em', 'code', 'a', 'hr']
});

メモリキャッシュ

const fullTextCache = new Map();

// 初回: API から取得 → キャッシュ
fullTextCache.set(article.id, fullText);

// 2回目以降: キャッシュから即表示
if (fullTextCache.has(article.id)) {
    return cachedText;  // APIコールなし
}

File: posimai-brain/style.css

Markdown CSS

.article-detail-md h1 { font-size: 24px; ... }
.article-detail-md h2 { font-size: 20px; ... }
.article-detail-md a { color: var(--accent); ... }
.article-detail-md code { background: var(--surface2); ... }

セキュリティ考慮

対策 実装内容 効果
認証 JWT/APIキー (authMiddleware) 未認証アクセス防止
XSS対策 DOMPurify でサニタイズ スクリプト埋め込み防止
SQL Injection パラメータ化クエリ ($1, $2, ...) SQL攻撃防止
HTTPS Vercel (自動), Tailscale (暗号化) 通信盗聴防止
CORS 設定済み (Brain API) 不正ドメインからのアクセス防止

パフォーマンス最適化

項目 実装 効果
AI トークン制限 本文 5000 文字まで コスト削減
メモリキャッシュ Map-based cache 2回目以降の表示が即座
ON CONFLICT PostgreSQL UPSERT 重複チェック不要
インデックス user_id, created_at クエリ高速化

トラブルシューティング

問題 1: 本文が保存されない

確認事項:

  1. Reader/Feed が content を送信しているか?
    • DevTools → Network → /save リクエストの body を確認
  2. Synology API が full_text カラムに保存しているか?
    • PostgreSQL: SELECT full_text FROM articles ORDER BY created_at DESC LIMIT 1;
  3. マイグレーションは実行済みか?
    • \d articlesfull_text カラムがあるか確認

問題 2: Markdown が表示されない

確認事項:

  1. DOMPurify が読み込まれているか?
    • DevTools → Console: window.DOMPurify が存在するか
  2. CSS が適用されているか?
    • DevTools → Elements: .article-detail-md クラスがあるか
  3. Markdown 変換が動作しているか?
    • DevTools → Console: renderMarkdown("# Test") を実行

問題 3: AI 分析が失敗する

確認事項:

  1. Gemini API キーは正しいか?
    • Synology: 環境変数 GEMINI_API_KEY を確認
  2. 本文が長すぎないか?
    • 5000 文字以上は自動でカットされる
  3. ログを確認
    • docker logs -f posimai-brain-api

まとめ

Phase 1 の「本文全文保存」機能により:

データロスなし: Reader で読んだ記事の本文が完全保存される 高精度AI分析: 本文全文から要約・トピック抽出 オフライン閲覧可: Brain に保存した記事は元サイトが消えても読める UX向上: メモリキャッシュで2回目以降の表示が高速 セキュア: XSS対策、認証、HTTPS完備

次のステップ: Synology 側の実装完了を確認し、エンドツーエンドテストを実施。