97 lines
3.8 KiB
JavaScript
97 lines
3.8 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const { getDb, ensureUserDefaultCategories } = require('../db/database');
|
|
const { standardizeError } = require('../middleware/errorFormatter');
|
|
const {
|
|
createSubscriptionFromRecommendation,
|
|
decorateSubscription,
|
|
getSubscriptionRecommendations,
|
|
getSubscriptionSummary,
|
|
getSubscriptions,
|
|
} = require('../services/subscriptionService');
|
|
|
|
router.get('/', (req, res) => {
|
|
const db = getDb();
|
|
ensureUserDefaultCategories(req.user.id);
|
|
const subscriptions = getSubscriptions(db, req.user.id);
|
|
res.json({
|
|
summary: getSubscriptionSummary(subscriptions),
|
|
subscriptions,
|
|
});
|
|
});
|
|
|
|
router.get('/recommendations', (req, res) => {
|
|
const db = getDb();
|
|
res.json({
|
|
recommendations: getSubscriptionRecommendations(db, req.user.id),
|
|
});
|
|
});
|
|
|
|
router.post('/recommendations/create', (req, res) => {
|
|
const db = getDb();
|
|
ensureUserDefaultCategories(req.user.id);
|
|
if (req.body?.category_id) {
|
|
const categoryId = parseInt(req.body.category_id, 10);
|
|
const category = Number.isInteger(categoryId)
|
|
? db.prepare('SELECT id FROM categories WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(categoryId, req.user.id)
|
|
: null;
|
|
if (!category) {
|
|
return res.status(400).json(standardizeError('category_id is invalid for this user', 'VALIDATION_ERROR', 'category_id'));
|
|
}
|
|
}
|
|
try {
|
|
const created = createSubscriptionFromRecommendation(db, req.user.id, req.body || {});
|
|
res.status(201).json(created);
|
|
} catch (err) {
|
|
res.status(err.status || 400).json(standardizeError(err.message || 'Could not create subscription', err.status ? 'VALIDATION_ERROR' : 'SUBSCRIPTION_CREATE_ERROR', err.field || null));
|
|
}
|
|
});
|
|
|
|
router.patch('/:id', (req, res) => {
|
|
const db = getDb();
|
|
const billId = parseInt(req.params.id, 10);
|
|
if (!Number.isInteger(billId)) {
|
|
return res.status(400).json(standardizeError('bill_id must be an integer', 'VALIDATION_ERROR', 'bill_id'));
|
|
}
|
|
|
|
const existing = db.prepare('SELECT * FROM bills WHERE id = ? AND user_id = ? AND deleted_at IS NULL').get(billId, req.user.id);
|
|
if (!existing) return res.status(404).json(standardizeError('Bill not found', 'NOT_FOUND', 'bill_id'));
|
|
|
|
const allowedTypes = new Set(['streaming', 'software', 'cloud', 'music', 'news', 'fitness', 'gaming', 'utilities', 'insurance', 'other']);
|
|
const next = {
|
|
is_subscription: req.body.is_subscription !== undefined ? (req.body.is_subscription ? 1 : 0) : existing.is_subscription,
|
|
subscription_type: req.body.subscription_type !== undefined
|
|
? (allowedTypes.has(req.body.subscription_type) ? req.body.subscription_type : 'other')
|
|
: existing.subscription_type,
|
|
reminder_days_before: req.body.reminder_days_before !== undefined
|
|
? Number(req.body.reminder_days_before)
|
|
: existing.reminder_days_before,
|
|
active: req.body.active !== undefined ? (req.body.active ? 1 : 0) : existing.active,
|
|
};
|
|
|
|
if (!Number.isInteger(next.reminder_days_before) || next.reminder_days_before < 0 || next.reminder_days_before > 30) {
|
|
return res.status(400).json(standardizeError('reminder_days_before must be between 0 and 30', 'VALIDATION_ERROR', 'reminder_days_before'));
|
|
}
|
|
|
|
db.prepare(`
|
|
UPDATE bills
|
|
SET is_subscription = ?,
|
|
subscription_type = ?,
|
|
reminder_days_before = ?,
|
|
active = ?,
|
|
updated_at = datetime('now')
|
|
WHERE id = ? AND user_id = ?
|
|
`).run(next.is_subscription, next.subscription_type, next.reminder_days_before, next.active, billId, req.user.id);
|
|
|
|
const updated = db.prepare(`
|
|
SELECT b.*, c.name AS category_name
|
|
FROM bills b
|
|
LEFT JOIN categories c ON c.id = b.category_id AND c.user_id = b.user_id AND c.deleted_at IS NULL
|
|
WHERE b.id = ? AND b.user_id = ?
|
|
`).get(billId, req.user.id);
|
|
|
|
res.json(decorateSubscription(updated));
|
|
});
|
|
|
|
module.exports = router;
|