BillTracker/routes/spending.js

118 lines
4.3 KiB
JavaScript
Raw Normal View History

'use strict';
const express = require('express');
const router = express.Router();
const { getDb } = require('../db/database');
const {
getSpendingSummary, getSpendingTransactions, categorizeTransaction,
getSpendingBudgets, setSpendingBudget,
getSpendingCategoryRules, addSpendingCategoryRule, deleteSpendingCategoryRule,
} = 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 });
res.json({ budgets: getSpendingBudgets(getDb(), req.user.id, ym.year, ym.month) });
});
// 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) {
res.status(500).json({ error: 'Failed to save budget' });
}
});
// GET /api/spending/category-rules
router.get('/category-rules', (req, res) => {
res.json({ rules: getSpendingCategoryRules(getDb(), req.user.id) });
});
// 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) {
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) => {
deleteSpendingCategoryRule(getDb(), req.user.id, parseInt(req.params.id, 10));
res.json({ ok: true });
});
module.exports = router;