526 lines
26 KiB
Markdown
526 lines
26 KiB
Markdown
|
|
# 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 側の実装完了を確認し、エンドツーエンドテストを実施。
|