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

11 KiB
Raw Blame History

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 のみ

結論: poolauthMiddleware を共有モジュールに切り出せば、残りのルート分割は機械的に行える。


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時間

ルートは一切触らない。共有変数をモジュールに切り出すだけ。

mkdir lib routes

lib/db.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

// JWT_SECRET, JWT_TTL_SECONDS, createToken, authMiddleware, session helpers
module.exports = { JWT_SECRET, createToken, authMiddleware };

lib/rateLimit.js

// rateLimitStores, rateLimit(), webauthnChallenges, cleanup interval
module.exports = { rateLimit, webauthnChallenges };

lib/fetch.js

// isSsrfSafe, fetchMeta, fetchFullTextViaJina, analyzeWithGemini, extractSource
module.exports = { isSsrfSafe, fetchMeta, fetchFullTextViaJina, analyzeWithGemini };

server.js 側の変更は require 置き換えのみ:

const { pool, initDB } = require('./lib/db');
const { authMiddleware } = require('./lib/auth');

動作確認:

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完了後

各ルートファイルの形式:

'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 でのマウント:

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」を末尾に追記してください。