posimai-root/docs/server-refactor-plan.md

267 lines
11 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.

# server.js リファクタリング計画
最終更新: 2026-04-10
対象: Claude Code / Cursor / Gemini / 全 AI エージェント
**このドキュメントは `server.js` の現状分析・分割設計・実施タイミング判断基準を一元管理します。
リファクタリング作業を始める前に必ず読んでください。**
---
## 0. 現状サマリー
| 項目 | 内容 |
|------|------|
| ファイル | `/server.js`(リポジトリルート) |
| 行数 | **3091行**2026-04-10 計測) |
| 役割 | VPS 上で動く Node.js/Express バックエンド。全アプリの API を1ファイルに集約 |
| 問題 | テストが書けない・障害切り分けが困難・新機能追加のたびにファイルが肥大化 |
| 状態 | **現時点では本番稼働中・障害なし。即時分割は不要。** |
---
## 1. 現在の内部構造(セクション別行数)
```
server.js (3091行)
├── L127 require / app 初期化
├── L2884 Auth helpersWebAuthn dynamic import、インメモリレートリミッター
├── L85126 JWT config + session helpers
├── L127173 Express middlewareCORS など)
├── L174260 DB pool (pg) + Gemini + API Key 認証マップ
├── L261481 共有ユーティリティextractSource、charset 正規化、SSRF guard、
│ fetchMeta、fetchFullTextViaJina、analyzeWithGemini
├── L482721 initDBPostgreSQL スキーマ全定義 — 240行
├── L722769 Express Router 生成 + /health
├── ── ルートグループ ─────────────────────────────────────────────
├── L7701106 Auth: Magic Link + session337行
├── L9261106 Auth: Google OAuth / GitHub OAuth181行※上記と連続
├── L11071354 Auth: Passkey / WebAuthn248行
│ 合計 Auth routes: 585行
├── L13551612 Brain articlesGET/POST/PATCH/DELETE + save + history = 258行
├── L16131685 Journal routesposts + public + AI tag suggestion + upload = 170行
├── L16861782 Site Config routes96行
├── L17831884 Habit routes102行
├── L18851930 Pulse routes46行
├── L19311967 Lens history routes37行
├── L19682118 Feed Media + Feed Articles routes151行
├── L21192288 TTSVOICEVOXroutes + サーバー側自動プリウォーム170行
├── L22892320 Events前処理32行
├── L23212775 Together routesgroups / join / share / feed / comments / search = 455行
├── L26252775 Atlas proxyGitHub / Vercel / Tailscale scan = 151行※上記と連続
├── L27762884 Doorkeeper + connpassapp-level、router 外 = 109行
├── L28852889 静的ファイル(/uploads
├── L28912989 Stripe webhook99行
└── L29903091 router マウント + サーバー起動 + Feed 背景ジョブ102行
```
---
## 2. 共有変数の依存マップ
分割時に「どの変数をどのファイルに置くか」の判断基準。
| 変数 / 関数 | 宣言行 | 参照箇所数 | 参照元セクション |
|------------|--------|-----------|----------------|
| `pool` (pg Pool) | L174 | **108箇所** | 全ルートファイル |
| `authMiddleware` | L197付近 | **46箇所** | 全認証必須ルート |
| `genAI` (Gemini) | L191 | 2箇所 | brain.js のみ |
| `webauthnChallenges` (Map) | L40 | 15箇所 | auth routes のみ |
| `rateLimitStores` / `rateLimit()` | L51 | 多数 | auth routes 中心 |
| `isSsrfSafe()` | L294 | 5箇所 | brain.js / together.js |
| `fetchMeta()` | L304 | brain + together |
| `fetchFullTextViaJina()` | L372 | brain + together |
| `analyzeWithGemini()` | L416 | brain のみ |
| `JWT_SECRET` | L86 | auth 全体 |
| `WEBAUTHN_RP_*` | L92 | auth/passkey のみ |
**結論**: `pool``authMiddleware` を共有モジュールに切り出せば、残りのルート分割は機械的に行える。
---
## 3. 目標とする分割後の構造
```
server.js ← 100行以内エントリポイント + マウントのみ)
lib/
db.js ← pool + initDB をエクスポート
auth.js ← JWT_SECRET / createToken / authMiddleware / session helpers
rateLimit.js ← インメモリレートリミッターwebauthnChallenges も含める)
fetch.js ← isSsrfSafe / fetchMeta / fetchFullTextViaJina / analyzeWithGemini
routes/
auth.js ← Magic Link + OAuthGoogle/GitHub+ Passkey/WebAuthn
brain.js ← articles / save / history / AI 分析
journal.js ← journal posts + site config
habit.js ← habit + pulse + lens history
feed.js ← feed media + feed articles + TTS + feed バックグラウンドジョブ
together.js ← groups / join / share / comments / search
events.js ← Doorkeeper + connpassapp-level も含めて統合)
atlas.js ← GitHub / Vercel / Tailscale scan proxy
stripe.js ← webhook のみ
```
**各ファイルの想定行数**: 100600行。神ファイルは完全に消える。
---
## 4. 実施タイミングの判断基準
### やらなくていい条件(すべて揃っている間は待つ)
- 本番で分割に起因する障害が出ていない
- 新しいルートグループを追加する予定がない
- 商用化タスクStripe 課金・ユーザー招待・モバイル)が残っている
### 開始するべきタイミングどれか1つ該当したら着手
| トリガー | 理由 |
|---------|------|
| **新ルートグループを追加するとき**(例: posimai-station の API | 新ファイルを作るついでに既存も整理。追加コストがほぼゼロ |
| **auth.js だけバグが続いて server.js 内のデバッグが辛い** | 問題のある1ルートだけ先に切り出す部分分割でも OK |
| **Node.js バージョンアップや pg ライブラリ更新のとき** | どうせ動作確認が必要なので、そのタイミングで進める |
| **server.js が 3500行を超えたとき** | 純粋に限界。この時点で強制着手 |
---
## 5. 実施手順(着手するときはこの順番で)
### フェーズ 1: 共有レイヤーの切り出し低リスク・12時間
ルートは一切触らない。共有変数をモジュールに切り出すだけ。
```bash
mkdir lib routes
```
**lib/db.js**
```js
'use strict';
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.DB_HOST || 'db',
port: parseInt(process.env.DB_PORT || '5432'),
user: process.env.DB_USER || 'gitea', // TODO: 'posimai' に変更
password: process.env.DB_PASSWORD || '',
database: 'posimai_brain',
max: 15,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 5000,
});
pool.on('error', (err) => console.error('[DB] Unexpected pool error:', err.message));
async function initDB() { /* server.js L482721 をそのまま移動 */ }
module.exports = { pool, initDB };
```
**lib/auth.js**
```js
// JWT_SECRET, JWT_TTL_SECONDS, createToken, authMiddleware, session helpers
module.exports = { JWT_SECRET, createToken, authMiddleware };
```
**lib/rateLimit.js**
```js
// rateLimitStores, rateLimit(), webauthnChallenges, cleanup interval
module.exports = { rateLimit, webauthnChallenges };
```
**lib/fetch.js**
```js
// isSsrfSafe, fetchMeta, fetchFullTextViaJina, analyzeWithGemini, extractSource
module.exports = { isSsrfSafe, fetchMeta, fetchFullTextViaJina, analyzeWithGemini };
```
server.js 側の変更は `require` 置き換えのみ:
```js
const { pool, initDB } = require('./lib/db');
const { authMiddleware } = require('./lib/auth');
```
**動作確認**:
```bash
node server.js
curl http://localhost:PORT/api/health
```
---
### フェーズ 2: ルートの切り出し中リスク・1ルート1PR
**独立度が高い順(この順番で進める)**:
```
1. routes/stripe.js 99行 依存: pool のみ。最も安全
2. routes/atlas.js 151行 依存: なし外部API プロキシのみ)
3. routes/events.js 141行 依存: なし外部API フェッチのみ)
4. routes/together.js 455行 依存: pool + authMiddleware + isSsrfSafe
5. routes/feed.js 321行 依存: pool + authMiddlewarefeed job は起動後アタッチ)
6. routes/habit.js 185行 依存: pool + authMiddleware
7. routes/brain.js 258行 依存: pool + authMiddleware + fetch + genAI
8. routes/journal.js 266行 依存: pool + authMiddleware
9. routes/auth.js 585行 依存: webauthnChallenges + rateLimitフェーズ1完了後
```
**各ルートファイルの形式**:
```js
'use strict';
const { Router } = require('express');
const { pool } = require('../lib/db');
const { authMiddleware } = require('../lib/auth');
// ...
const router = Router();
router.get('/xxx', authMiddleware, async (req, res) => { ... });
module.exports = router;
```
**server.js でのマウント**:
```js
const stripeRouter = require('./routes/stripe');
app.use('/brain/api/stripe', stripeRouter);
app.use('/api/stripe', stripeRouter);
```
---
### フェーズ 2 の注意点(ハマりやすい箇所)
1. **feed バックグラウンドジョブ**L29983091は routes/feed.js に移すが、`startFeedJob()` の呼び出しは server.js の起動後に行う必要がある。`module.exports = { router, startFeedJob }` の形式にする。
2. **Doorkeeper / connpass の二重定義**L27762884 が router 外の app-levelは events.js に統合し、router 内に移動する。`/brain/api/events/*` と `/api/events/*` の両マウントを忘れずに。
3. **auth routes の webauthnChallenges** は auth.js ルートファイルと lib/rateLimit.jsまたはlib/auth.jsが共有するため、フェーズ1の lib 切り出し完了後でないと移動できない。必ずフェーズ1の後に行う。
---
## 6. やらないこと(スコープ外)
| 項目 | 理由 |
|------|------|
| TypeScript 化 | 全書き直しになる。現在の優先度では対費用効果が出ない |
| テストコード追加 | 分割と同時にやると範囲が広がりすぎる。分割後の別タスク |
| DB_USER: 'gitea' の修正 | サーバー側 PostgreSQL ユーザー変更が伴う。別タスク |
| console.log 整理 | 分割と無関係。別タスク |
| インメモリ state の永続化 | スキーマ変更が必要。別タスクCLAUDE.md 要確認事項) |
---
## 7. 検討中の追加改善(分割後の次フェーズ候補)
- **DB_USER を 'gitea' から 'posimai' に変更**PostgreSQL ユーザー作成が必要)
- **webauthnChallenges / rateLimitStores の Redis 移行**(再起動耐性・スケール対応)
- **console.log を構造化ログpino 等)に統一**
- **ルートごとのエラーハンドリング統一**(現状は各ルートで個別 try/catch
---
*このドキュメントは作業開始時・完了時に更新すること。*
*分割を実施した AI は「フェーズ X 完了」「完了日」「担当 AI」を末尾に追記してください。*