250 lines
11 KiB
Markdown
250 lines
11 KiB
Markdown
|
|
# Phase 1: Brain 本文全文保存 — Synology 側 実施手順(コピペ用)
|
|||
|
|
|
|||
|
|
**目的**: Reader から送られた本文を Brain API が受け取り DB に保存し、AI 分析で本文を使うようにする。
|
|||
|
|
|
|||
|
|
**前提**: Synology NAS に SSH で接続できること。Brain API と PostgreSQL がすでに動いていること。
|
|||
|
|
|
|||
|
|
**補足**: Synology の「Container Manager」は従来の「Docker」パッケージの後継です。中身は同じなので、SSH では `docker` コマンドが使えます。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## あなたの環境(docker ps の結果)について
|
|||
|
|
|
|||
|
|
| コンテナ名 | イメージ | 用途の目安 |
|
|||
|
|
|---------------|-----------------|--------------------|
|
|||
|
|
| **posimai_api** | node:20-slim (8090) | **Brain API** の可能性が高い |
|
|||
|
|
| **gitea_db** | postgres:15 | **Gitea 用** PostgreSQL(Brain 用ではない) |
|
|||
|
|
| その他 | gitea, mcp_server, uptime-kuma 等 | - |
|
|||
|
|
|
|||
|
|
**Brain 専用の PostgreSQL コンテナは一覧にありません。**
|
|||
|
|
|
|||
|
|
- **posimai_api** が Brain API で、その中で **SQLite** や **別の DB** を使っている場合は、手順 1 の「PostgreSQL に接続」は**そのままでは使えません**。
|
|||
|
|
- まず **posimai_api(Brain API)のコードや設定**を確認し、「どの DB に接続しているか」を把握する必要があります。
|
|||
|
|
|
|||
|
|
**進め方の目安**
|
|||
|
|
|
|||
|
|
1. **Brain 用に PostgreSQL を別コンテナで用意している場合**
|
|||
|
|
→ そのコンテナ名で手順 1 の「1-3. PostgreSQL に接続」を実行し、手順 1 → 2 の順で実施。
|
|||
|
|
|
|||
|
|
2. **Brain が SQLite など PostgreSQL 以外を使っている場合**
|
|||
|
|
→ **手順 1 はいったん飛ばし、手順 2(API のコード修正)だけ先に実施**してください。
|
|||
|
|
保存処理で `full_text` を扱うようにし、使っている DB のスキーマに `full_text` カラム(または相当の項目)を後から追加する形になります。
|
|||
|
|
(SQLite なら、posimai_api のプロジェクト内のマイグレーションや、DB ファイルを開いて `ALTER TABLE` する方法などがあります。)
|
|||
|
|
|
|||
|
|
3. **どこに DB があるか分からない場合**
|
|||
|
|
posimai_api の設定ファイル(例: `.env` / `config.js` / 環境変数)や、ソース内の `pool` / `createPool` / `sqlite` などの記述を確認すると、接続先が分かります。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 手順 1: データベースにカラムを追加する(Brain が PostgreSQL を使っている場合のみ)
|
|||
|
|
|
|||
|
|
### 1-1. Synology に SSH 接続
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
ssh admin@posimai-lab.tail72e846.ts.net
|
|||
|
|
```
|
|||
|
|
(パスワードを聞かれたら入力。ユーザー名が `mai` の場合は `ssh mai@posimai-lab.tail72e846.ts.net`)
|
|||
|
|
|
|||
|
|
### 1-2. コンテナ名を確認する(重要)
|
|||
|
|
|
|||
|
|
手順書の `posimai-brain-postgres` は**例**です。お使いの環境ではコンテナ名が違う場合があります。
|
|||
|
|
|
|||
|
|
**方法A: コマンドで一覧を表示**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker ps
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
表示された一覧の「NAMES」列で、PostgreSQL のコンテナ名を確認してください(例: `postgres` / `brain-postgres` / `synology-postgres` など)。
|
|||
|
|
|
|||
|
|
**方法B: DSM の Container Manager で確認**
|
|||
|
|
|
|||
|
|
1. DSM にログイン → **Container Manager** を開く
|
|||
|
|
2. 「コンテナ」一覧で、PostgreSQL または Brain 用 DB のコンテナを探す
|
|||
|
|
3. コンテナ名(名前の列)をメモする
|
|||
|
|
|
|||
|
|
**次の 1-3 で使うコマンドの `コンテナ名` を、上で確認した名前に置き換えてください。**
|
|||
|
|
|
|||
|
|
### 1-3. PostgreSQL に接続
|
|||
|
|
|
|||
|
|
**Container Manager(Docker)で PostgreSQL を動かしている場合:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker exec -it コンテナ名 psql -U brain_user -d brain_db
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `コンテナ名` を 1-2 で確認した名前に置き換えます。
|
|||
|
|
- 例: コンテナ名が `postgres` なら
|
|||
|
|
`docker exec -it postgres psql -U brain_user -d brain_db`
|
|||
|
|
- ユーザー名・DB 名(`brain_user` / `brain_db`)が違う場合は、実際の設定に合わせて変更してください。
|
|||
|
|
|
|||
|
|
**PostgreSQL を Docker 以外で動かしている場合(NAS に直接インストール等):**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
psql -U brain_user -d brain_db
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1-4. 以下の SQL をそのままコピーして貼り付け、Enter で実行
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- full_text カラムを追加(本文全文)
|
|||
|
|
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();
|
|||
|
|
|
|||
|
|
-- 確認(articles の構造が表示されればOK)
|
|||
|
|
\d articles
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
`\d articles` の結果に `full_text` が含まれていれば成功です。
|
|||
|
|
|
|||
|
|
### 1-5. PostgreSQL を終了
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
\q
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 手順 2: Brain API のコードを修正する
|
|||
|
|
|
|||
|
|
**posimai_api** が Brain API の場合、そのコンテナで動いているコード(NAS 上でマウントしているフォルダ内の `server.js` など)を編集します。
|
|||
|
|
|
|||
|
|
Brain API の `server.js`(またはルーターを定義しているファイル)を編集します。
|
|||
|
|
|
|||
|
|
**編集する場所の目安:**
|
|||
|
|
- `POST /save` または `POST /brain/api/save` を処理しているブロック
|
|||
|
|
- `GET /articles` または `GET /brain/api/articles` を処理しているブロック
|
|||
|
|
|
|||
|
|
### 2-1. POST /save の置き換え
|
|||
|
|
|
|||
|
|
**いまの `post('/save', ...)` ~ そのブロックの終わりまで** を、以下で**まるごと置き換え**してください。
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// POST /save — Reader から url, title, content, source を受け取り本文を保存
|
|||
|
|
app.post('/save', authMiddleware, async (req, res) => {
|
|||
|
|
const { url, title, content, source } = req.body || {};
|
|||
|
|
if (!url || !title) {
|
|||
|
|
return res.status(400).json({ error: 'Missing required fields: url and title' });
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
let fullText = content || null;
|
|||
|
|
let meta = {};
|
|||
|
|
if (!fullText || fullText.trim().length === 0) {
|
|||
|
|
meta = await fetchOGP(url); // 既存の OGP 取得関数
|
|||
|
|
fullText = meta.desc || '';
|
|||
|
|
} else {
|
|||
|
|
meta = await fetchOGP(url);
|
|||
|
|
}
|
|||
|
|
const ai = await analyzeWithGemini(title, fullText, url); // 第2引数を fullText に変更
|
|||
|
|
const result = await pool.query(`
|
|||
|
|
INSERT INTO articles (user_id, url, title, full_text, summary, topics, source, reading_time, favicon, og_image, status, created_at, updated_at)
|
|||
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'inbox', NOW(), NOW())
|
|||
|
|
ON CONFLICT (user_id, url) DO UPDATE SET
|
|||
|
|
title = EXCLUDED.title,
|
|||
|
|
full_text = EXCLUDED.full_text,
|
|||
|
|
summary = EXCLUDED.summary,
|
|||
|
|
topics = EXCLUDED.topics,
|
|||
|
|
reading_time = EXCLUDED.reading_time,
|
|||
|
|
favicon = EXCLUDED.favicon,
|
|||
|
|
og_image = EXCLUDED.og_image,
|
|||
|
|
updated_at = NOW()
|
|||
|
|
RETURNING id
|
|||
|
|
`, [req.userId, url, title, fullText, ai.summary, ai.topics, source || 'reader', ai.readingTime, meta.favicon || null, meta.ogImage || null]);
|
|||
|
|
const articleId = result.rows[0].id;
|
|||
|
|
return res.json({ success: true, articleId, fullTextSaved: !!fullText, textLength: fullText?.length || 0 });
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Brain API] Save failed:', error);
|
|||
|
|
return res.status(500).json({ error: 'Failed to save article', message: error.message });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**あわせて確認すること:**
|
|||
|
|
- `analyzeWithGemini` の**第2引数**を、これまでの「短い説明」ではなく **本文全文(fullText)** に変更する。
|
|||
|
|
- `fetchOGP` はそのまま利用して問題ありません。
|
|||
|
|
|
|||
|
|
### 2-2. GET /articles の置き換え
|
|||
|
|
|
|||
|
|
**いまの `get('/articles', ...)` ~ そのブロックの終わりまで** を、以下で**まるごと置き換え**してください。
|
|||
|
|
|
|||
|
|
```javascript
|
|||
|
|
// GET /articles — 一覧取得時に full_text も返す(Brain UI で本文表示するため)
|
|||
|
|
app.get('/articles', authMiddleware, async (req, res) => {
|
|||
|
|
try {
|
|||
|
|
const { rows } = await pool.query(`
|
|||
|
|
SELECT id, url, title, full_text, summary, topics, source, reading_time, favicon, og_image, status, created_at, updated_at
|
|||
|
|
FROM articles
|
|||
|
|
WHERE user_id = $1
|
|||
|
|
ORDER BY created_at DESC
|
|||
|
|
`, [req.userId]);
|
|||
|
|
return res.json({
|
|||
|
|
articles: rows.map(row => ({
|
|||
|
|
id: row.id,
|
|||
|
|
url: row.url,
|
|||
|
|
title: row.title,
|
|||
|
|
fullText: row.full_text,
|
|||
|
|
summary: row.summary,
|
|||
|
|
topics: row.topics,
|
|||
|
|
source: row.source,
|
|||
|
|
readingTime: row.reading_time,
|
|||
|
|
favicon: row.favicon,
|
|||
|
|
ogImage: row.og_image,
|
|||
|
|
status: row.status,
|
|||
|
|
createdAt: row.created_at,
|
|||
|
|
updatedAt: row.updated_at
|
|||
|
|
}))
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error('[Brain API] Failed to fetch articles:', error);
|
|||
|
|
return res.status(500).json({ error: 'Failed to fetch articles', message: error.message });
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**注意:** お使いのコードが `app` ではなく `router` の場合は、上記の `app.post` / `app.get` を `router.post` / `router.get` に読み替えてください。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 手順 3: API サーバーを再起動する
|
|||
|
|
|
|||
|
|
**Docker で Brain API を動かしている場合:**
|
|||
|
|
|
|||
|
|
```bash
|
|||
|
|
docker restart posimai-brain-api
|
|||
|
|
docker logs -f posimai-brain-api
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
ログにエラーが出ていなければ成功です。`Ctrl+C` でログ表示を終了できます。
|
|||
|
|
|
|||
|
|
**Docker を使っていない場合:**
|
|||
|
|
Brain API を起動している方法(systemd や手動実行など)に合わせて再起動してください。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 手順 4: 動作確認
|
|||
|
|
|
|||
|
|
1. **Reader** で記事を開き、「Brainに保存」を押す。
|
|||
|
|
2. ブラウザの開発者ツール(F12)の「ネットワーク」で、`/save` へのレスポンスを確認する。
|
|||
|
|
- `fullTextSaved: true` かつ `textLength` が数百以上なら、本文が送れて保存されています。
|
|||
|
|
3. **Brain** の一覧を開き、該当記事の「本文を読む」が出ていれば、API と DB の対応は問題ありません。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## うまくいかないとき
|
|||
|
|
|
|||
|
|
- **「column full_text does not exist」**
|
|||
|
|
→ 手順 1 の SQL が実行されていないか、別の DB を見ている可能性があります。もう一度 1-2~1-4 を実行してください。
|
|||
|
|
- **「analyzeWithGemini is not a function」**
|
|||
|
|
→ `analyzeWithGemini` の定義で、第2引数が「本文」になるように変更してください(従来の短い説明用引数を fullText に変更)。
|
|||
|
|
- **Reader から保存しても Brain に反映されない**
|
|||
|
|
→ Synology の Brain API のログ(`docker logs posimai-brain-api`)でエラーが出ていないか確認してください。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**次のステップ:** Brain のフロント(Vercel など)は、すでに「本文を読む」で保存済み本文を表示できるように修正済みです。Synology 側の上記対応が完了すれば Phase 1 は完了です。
|