posimai-root/docs/synology/SYNOLOGY_SAVE_FLOW_DIAGRAM.md

526 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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](synology-brain-migration.sql)
```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](synology-brain-api-save-endpoint.js)
#### POST /save の変更点
- **Before**: `{ url, title, source }` のみ
- **After**: `{ url, title, content, source }``content` 追加
```javascript
// 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` を含めて返す
```javascript
// 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](posimai-brain/js/ui/actions.js)
#### Markdown レンダリング
```javascript
// 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']
});
```
#### メモリキャッシュ
```javascript
const fullTextCache = new Map();
// 初回: API から取得 → キャッシュ
fullTextCache.set(article.id, fullText);
// 2回目以降: キャッシュから即表示
if (fullTextCache.has(article.id)) {
return cachedText; // APIコールなし
}
```
**File**: [posimai-brain/style.css](posimai-brain/style.css)
#### Markdown CSS
```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 articles``full_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 側の実装完了を確認し、エンドツーエンドテストを実施。