'use strict'; const express = require('express'); const router = express.Router(); const { getDb } = require('../db/database'); const { getSpendingSummary, getSpendingTransactions, categorizeTransaction, getSpendingBudgets, setSpendingBudget, getSpendingCategoryRules, addSpendingCategoryRule, deleteSpendingCategoryRule, getIncomeTransactions, } = require('../services/spendingService'); function parseYM(source) { const now = new Date(); const year = parseInt(source.year || now.getFullYear(), 10); const month = parseInt(source.month || now.getMonth() + 1, 10); if (isNaN(year) || year < 2000 || year > 2100) return { error: 'Invalid year' }; if (isNaN(month) || month < 1 || month > 12) return { error: 'Invalid month' }; return { year, month }; } // GET /api/spending/summary?year=&month= router.get('/summary', (req, res) => { const ym = parseYM(req.query); if (ym.error) return res.status(400).json({ error: ym.error }); try { res.json(getSpendingSummary(getDb(), req.user.id, ym.year, ym.month)); } catch (err) { console.error('[spending/summary]', err.message); res.status(500).json({ error: 'Failed to load spending summary' }); } }); // GET /api/spending/transactions?year=&month=&category_id=&page=&limit= router.get('/transactions', (req, res) => { const ym = parseYM(req.query); if (ym.error) return res.status(400).json({ error: ym.error }); const { category_id, page, limit } = req.query; const categoryId = category_id === 'null' ? null : category_id !== undefined ? parseInt(category_id, 10) : undefined; try { res.json(getSpendingTransactions(getDb(), req.user.id, ym.year, ym.month, { categoryId, uncategorizedOnly: category_id === 'null', page: parseInt(page || '1', 10), limit: Math.min(parseInt(limit || '50', 10), 200), })); } catch (err) { console.error('[spending/transactions]', err.message); res.status(500).json({ error: 'Failed to load transactions' }); } }); // PATCH /api/spending/transactions/:id/category router.patch('/transactions/:id/category', (req, res) => { const txId = parseInt(req.params.id, 10); if (isNaN(txId)) return res.status(400).json({ error: 'Invalid transaction ID' }); const { category_id, save_rule } = req.body || {}; const categoryId = category_id === null || category_id === undefined ? null : parseInt(category_id, 10); try { categorizeTransaction(getDb(), req.user.id, txId, categoryId, !!save_rule); res.json({ ok: true }); } catch (err) { res.status(err.status || 500).json({ error: err.message || 'Failed to categorize transaction' }); } }); // GET /api/spending/budgets?year=&month= router.get('/budgets', (req, res) => { const ym = parseYM(req.query); if (ym.error) return res.status(400).json({ error: ym.error }); try { res.json({ budgets: getSpendingBudgets(getDb(), req.user.id, ym.year, ym.month) }); } catch (err) { console.error('[spending/budgets GET]', err.message); res.status(500).json({ error: 'Failed to load budgets' }); } }); // PUT /api/spending/budgets — { category_id, year, month, amount } router.put('/budgets', (req, res) => { const { category_id, year, month, amount } = req.body || {}; if (!category_id) return res.status(400).json({ error: 'category_id required' }); const ym = parseYM({ year, month }); if (ym.error) return res.status(400).json({ error: ym.error }); try { setSpendingBudget(getDb(), req.user.id, parseInt(category_id, 10), ym.year, ym.month, amount ?? null); res.json({ ok: true }); } catch (err) { console.error('[spending/budgets PUT]', err.message); res.status(500).json({ error: 'Failed to save budget' }); } }); // GET /api/spending/category-rules router.get('/category-rules', (req, res) => { try { res.json({ rules: getSpendingCategoryRules(getDb(), req.user.id) }); } catch (err) { console.error('[spending/category-rules GET]', err.message); res.status(500).json({ error: 'Failed to load rules' }); } }); // POST /api/spending/category-rules — { category_id, merchant } router.post('/category-rules', (req, res) => { const { category_id, merchant } = req.body || {}; if (!category_id || !merchant) return res.status(400).json({ error: 'category_id and merchant required' }); try { addSpendingCategoryRule(getDb(), req.user.id, parseInt(category_id, 10), merchant); res.json({ ok: true }); } catch (err) { console.error('[spending/category-rules POST]', err.message); res.status(err.status || 500).json({ error: err.message || 'Failed to save rule' }); } }); // DELETE /api/spending/category-rules/:id router.delete('/category-rules/:id', (req, res) => { try { deleteSpendingCategoryRule(getDb(), req.user.id, parseInt(req.params.id, 10)); res.json({ ok: true }); } catch (err) { console.error('[spending/category-rules DELETE]', err.message); res.status(500).json({ error: 'Failed to delete rule' }); } }); // GET /api/spending/income?year=&month=&page= router.get('/income', (req, res) => { const ym = parseYM(req.query); if (ym.error) return res.status(400).json({ error: ym.error }); try { res.json(getIncomeTransactions(getDb(), req.user.id, ym.year, ym.month, { page: parseInt(req.query.page || '1', 10), limit: Math.min(parseInt(req.query.limit || '50', 10), 200), includeIgnored: req.query.include_ignored === 'true', })); } catch (err) { console.error('[spending/income]', err.message); res.status(500).json({ error: 'Failed to load income transactions' }); } }); module.exports = router;