Vacay drag-to-paint, "Everyone" button, live exchange rates
- Vacay: click-and-drag to paint/erase vacation days across calendar - Vacay: "Everyone" button sets days for all persons (2+ only) - Budget: live currency conversion via frankfurter.app (cached 1h) - Budget: conversion widget in total card with selectable target currency - Day planner: remove transport mode buttons from day view
This commit is contained in:
@@ -107,6 +107,23 @@ app.use('/api/weather', weatherRoutes);
|
||||
app.use('/api/settings', settingsRoutes);
|
||||
app.use('/api/backup', backupRoutes);
|
||||
|
||||
// Exchange rates (cached 1h, authenticated)
|
||||
const { authenticate: rateAuth } = require('./middleware/auth');
|
||||
let _rateCache = { data: null, ts: 0 };
|
||||
app.get('/api/exchange-rates', rateAuth, async (req, res) => {
|
||||
const now = Date.now();
|
||||
if (_rateCache.data && now - _rateCache.ts < 3600000) return res.json(_rateCache.data);
|
||||
try {
|
||||
const r = await fetch('https://api.frankfurter.app/latest?from=EUR');
|
||||
if (!r.ok) return res.status(502).json({ error: 'Failed to fetch rates' });
|
||||
const data = await r.json();
|
||||
_rateCache = { data, ts: now };
|
||||
res.json(data);
|
||||
} catch {
|
||||
res.status(502).json({ error: 'Failed to fetch rates' });
|
||||
}
|
||||
});
|
||||
|
||||
// Serve static files in production
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
const publicPath = path.join(__dirname, '../public');
|
||||
|
||||
@@ -453,10 +453,28 @@ router.post('/entries/toggle', (req, res) => {
|
||||
const { date, target_user_id } = req.body;
|
||||
if (!date) return res.status(400).json({ error: 'date required' });
|
||||
const planId = getActivePlanId(req.user.id);
|
||||
const planUsers = getPlanUsers(planId);
|
||||
|
||||
// Toggle for all users in plan
|
||||
if (target_user_id === 'all') {
|
||||
const actions = [];
|
||||
for (const u of planUsers) {
|
||||
const existing = db.prepare('SELECT id FROM vacay_entries WHERE user_id = ? AND date = ? AND plan_id = ?').get(u.id, date, planId);
|
||||
if (existing) {
|
||||
db.prepare('DELETE FROM vacay_entries WHERE id = ?').run(existing.id);
|
||||
actions.push({ user_id: u.id, action: 'removed' });
|
||||
} else {
|
||||
db.prepare('INSERT INTO vacay_entries (plan_id, user_id, date, note) VALUES (?, ?, ?, ?)').run(planId, u.id, date, '');
|
||||
actions.push({ user_id: u.id, action: 'added' });
|
||||
}
|
||||
}
|
||||
notifyPlanUsers(planId, req.user.id);
|
||||
return res.json({ action: 'toggled_all', actions });
|
||||
}
|
||||
|
||||
// Allow toggling for another user if they are in the same plan
|
||||
let userId = req.user.id;
|
||||
if (target_user_id && parseInt(target_user_id) !== req.user.id) {
|
||||
const planUsers = getPlanUsers(planId);
|
||||
const tid = parseInt(target_user_id);
|
||||
if (!planUsers.find(u => u.id === tid)) {
|
||||
return res.status(403).json({ error: 'User not in plan' });
|
||||
|
||||
Reference in New Issue
Block a user