# 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 │ │ ) │ │ ・# →