diff --git a/server.js b/server.js index 4e911625..b9bd6a28 100644 --- a/server.js +++ b/server.js @@ -147,7 +147,6 @@ async function fetchMeta(url) { const title = og('og:title') || doc.querySelector('title')?.text || url; const desc = og('og:description') || meta('description') || ''; const img = og('og:image') || ''; - let host; // Declare host here for broader scope // Google Favicon API(優先)→ favicon.ico(フォールバック) const faviconUrl = `https://www.google.com/s2/favicons?domain=${new URL(url).hostname}&sz=32`; @@ -266,7 +265,7 @@ ${smartExtract(fullText || '', 5000)} console.error('[Gemini] Raw response:', result.response.text()); } return { - summary: `⚠️ AI分析失敗: ${String(e)}`, + summary: 'AI分析に失敗しました。しばらく後にお試しください。', topics: ['その他'], readingTime: 3 }; @@ -441,50 +440,7 @@ async function initDB() { `ALTER TABLE site_config ADD COLUMN IF NOT EXISTS user_id VARCHAR(50) NOT NULL DEFAULT 'maita'`, `ALTER TABLE site_config DROP CONSTRAINT IF EXISTS site_config_pkey`, `ALTER TABLE site_config ADD PRIMARY KEY (user_id, key)`, - // together テーブルをクリーンリセット(古いスキーマを完全に作り直す) - `DROP TABLE IF EXISTS together_comments, together_reactions, together_shares, together_members, together_groups CASCADE`, - `CREATE TABLE together_groups ( - id SERIAL PRIMARY KEY, - invite_code VARCHAR(8) UNIQUE NOT NULL, - name VARCHAR(100) NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() - )`, - `CREATE TABLE together_members ( - group_id INTEGER REFERENCES together_groups(id) ON DELETE CASCADE, - username VARCHAR(50) NOT NULL, - joined_at TIMESTAMPTZ DEFAULT NOW(), - PRIMARY KEY (group_id, username) - )`, - `CREATE TABLE together_shares ( - id SERIAL PRIMARY KEY, - group_id INTEGER REFERENCES together_groups(id) ON DELETE CASCADE, - shared_by VARCHAR(50) NOT NULL, - url TEXT, - title TEXT, - message TEXT NOT NULL DEFAULT '', - og_image TEXT, - tags TEXT[] DEFAULT '{}', - full_content TEXT, - summary TEXT, - archive_status VARCHAR(10) NOT NULL DEFAULT 'pending', - shared_at TIMESTAMPTZ DEFAULT NOW() - )`, - `CREATE INDEX IF NOT EXISTS idx_together_shares_group ON together_shares(group_id, shared_at DESC)`, - `CREATE TABLE together_reactions ( - share_id INTEGER REFERENCES together_shares(id) ON DELETE CASCADE, - username VARCHAR(50) NOT NULL, - type VARCHAR(20) NOT NULL DEFAULT 'like', - created_at TIMESTAMPTZ DEFAULT NOW(), - PRIMARY KEY (share_id, username, type) - )`, - `CREATE TABLE together_comments ( - id SERIAL PRIMARY KEY, - share_id INTEGER REFERENCES together_shares(id) ON DELETE CASCADE, - username VARCHAR(50) NOT NULL, - body TEXT NOT NULL, - created_at TIMESTAMPTZ DEFAULT NOW() - )`, - `CREATE INDEX IF NOT EXISTS idx_together_comments_share ON together_comments(share_id, created_at)`, + // together スキーマは schema 配列の CREATE TABLE IF NOT EXISTS で管理 ]; for (const sql of migrations) { await pool.query(sql).catch(e => console.warn('[DB] Migration warning:', e.message)); @@ -728,7 +684,7 @@ function buildRouter() { // GET /api/history - 履歴取得 r.get('/history', authMiddleware, async (req, res) => { - const limit = Math.min(parseInt(req.query.limit || '50'), 100); + const limit = Math.min(parseInt(req.query.limit || '50') || 50, 100); try { const result = await pool.query(` @@ -755,7 +711,7 @@ function buildRouter() { // ?user=maita でユーザー指定可能(将来の独立サイト対応) r.get('/journal/posts/public', async (req, res) => { try { - const limit = Math.min(parseInt(req.query.limit || '50'), 100); + const limit = Math.min(parseInt(req.query.limit || '50') || 50, 100); const userId = req.query.user || null; const { rows } = userId ? await pool.query( @@ -1006,7 +962,7 @@ ${excerpt} // GET /habit/heatmap — 過去 N 日分のチェック数(ヒートマップ用) r.get('/habit/heatmap', authMiddleware, async (req, res) => { - const days = Math.min(parseInt(req.query.days || '90'), 365); + const days = Math.min(parseInt(req.query.days || '90') || 90, 365); try { const { rows } = await pool.query(` SELECT log_date::text AS date, COUNT(*) AS count @@ -1039,7 +995,11 @@ ${excerpt} INSERT INTO pulse_log (user_id, log_date, mood, energy, focus, note, updated_at) VALUES ($1,$2,$3,$4,$5,$6,NOW()) ON CONFLICT (user_id, log_date) DO UPDATE - SET mood=$3, energy=$4, focus=$5, note=$6, updated_at=NOW() + SET mood=COALESCE($3, pulse_log.mood), + energy=COALESCE($4, pulse_log.energy), + focus=COALESCE($5, pulse_log.focus), + note=COALESCE(NULLIF($6,''), pulse_log.note), + updated_at=NOW() RETURNING mood, energy, focus, note `, [req.userId, req.params.date, mood || null, energy || null, focus || null, note]); res.json({ entry: rows[0] }); @@ -1048,7 +1008,7 @@ ${excerpt} // GET /pulse/log — 範囲取得(デフォルト直近30日) r.get('/pulse/log', authMiddleware, async (req, res) => { - const days = Math.min(parseInt(req.query.days || '30'), 365); + const days = Math.min(parseInt(req.query.days || '30') || 30, 365); try { const { rows } = await pool.query(` SELECT log_date::text AS date, mood, energy, focus, note @@ -1063,7 +1023,7 @@ ${excerpt} // ── Lens API ────────────────────────────── // GET /lens/history — スキャン履歴取得(直近 limit 件) r.get('/lens/history', authMiddleware, async (req, res) => { - const limit = Math.min(parseInt(req.query.limit || '20'), 100); + const limit = Math.min(parseInt(req.query.limit || '20') || 20, 100); try { const { rows } = await pool.query( 'SELECT id, filename, exif_data, thumbnail, scanned_at FROM lens_history WHERE user_id=$1 ORDER BY scanned_at DESC LIMIT $2', @@ -1409,6 +1369,7 @@ ${excerpt} // GET /together/groups/:groupId — グループ情報 r.get('/together/groups/:groupId', async (req, res) => { + if (!/^[a-zA-Z0-9_-]+$/.test(req.params.groupId)) return res.status(400).json({ error: 'invalid groupId' }); try { const result = await pool.query('SELECT * FROM together_groups WHERE id=$1', [req.params.groupId]); if (result.rows.length === 0) return res.status(404).json({ error: 'グループが見つかりません' });