- Express backend with PostgreSQL (JWT auth, full CRUD) - React + Vite + TailwindCSS frontend in Hebrew (RTL) - Features: Digital Booking System, Guest Management, Smart Budget Management - Docker Compose with postgres healthcheck - Auto-runs migrations on startup Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
110 lines
4.1 KiB
JavaScript
110 lines
4.1 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const pool = require('../db');
|
|
const { authMiddleware } = require('./auth');
|
|
|
|
// Get all budget items for an event
|
|
router.get('/event/:eventId', authMiddleware, async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`SELECT b.* FROM budget_items b
|
|
JOIN events e ON b.event_id = e.id
|
|
WHERE b.event_id = $1 AND e.user_id = $2
|
|
ORDER BY b.category, b.created_at ASC`,
|
|
[req.params.eventId, req.userId]
|
|
);
|
|
res.json(result.rows);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Get budget summary for event
|
|
router.get('/event/:eventId/summary', authMiddleware, async (req, res) => {
|
|
try {
|
|
const [itemsRes, eventRes] = await Promise.all([
|
|
pool.query(
|
|
`SELECT category,
|
|
SUM(estimated_cost) as estimated,
|
|
SUM(COALESCE(actual_cost, 0)) as actual,
|
|
COUNT(*) as count
|
|
FROM budget_items b
|
|
JOIN events e ON b.event_id = e.id
|
|
WHERE b.event_id = $1 AND e.user_id = $2
|
|
GROUP BY category`,
|
|
[req.params.eventId, req.userId]
|
|
),
|
|
pool.query('SELECT budget FROM events WHERE id=$1 AND user_id=$2', [req.params.eventId, req.userId]),
|
|
]);
|
|
const totalEstimated = itemsRes.rows.reduce((s, r) => s + parseFloat(r.estimated || 0), 0);
|
|
const totalActual = itemsRes.rows.reduce((s, r) => s + parseFloat(r.actual || 0), 0);
|
|
const eventBudget = eventRes.rows[0]?.budget || 0;
|
|
res.json({
|
|
event_budget: parseFloat(eventBudget),
|
|
total_estimated: totalEstimated,
|
|
total_actual: totalActual,
|
|
remaining_budget: parseFloat(eventBudget) - totalEstimated,
|
|
over_budget: totalEstimated > parseFloat(eventBudget),
|
|
categories: itemsRes.rows,
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Create budget item
|
|
router.post('/', authMiddleware, async (req, res) => {
|
|
const { event_id, category, description, estimated_cost, actual_cost, status } = req.body;
|
|
if (!event_id || !category) return res.status(400).json({ error: 'event_id and category are required' });
|
|
try {
|
|
const eventCheck = await pool.query('SELECT id FROM events WHERE id=$1 AND user_id=$2', [event_id, req.userId]);
|
|
if (eventCheck.rows.length === 0) return res.status(403).json({ error: 'Forbidden' });
|
|
|
|
const result = await pool.query(
|
|
`INSERT INTO budget_items (event_id, category, description, estimated_cost, actual_cost, status)
|
|
VALUES ($1, $2, $3, $4, $5, $6) RETURNING *`,
|
|
[event_id, category, description, estimated_cost || 0, actual_cost, status || 'planned']
|
|
);
|
|
res.status(201).json(result.rows[0]);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Update budget item
|
|
router.put('/:id', authMiddleware, async (req, res) => {
|
|
const { category, description, estimated_cost, actual_cost, status } = req.body;
|
|
try {
|
|
const result = await pool.query(
|
|
`UPDATE budget_items SET category=$1, description=$2, estimated_cost=$3, actual_cost=$4, status=$5
|
|
WHERE id=$6 AND event_id IN (SELECT id FROM events WHERE user_id=$7) RETURNING *`,
|
|
[category, description, estimated_cost, actual_cost, status, req.params.id, req.userId]
|
|
);
|
|
if (result.rows.length === 0) return res.status(404).json({ error: 'Budget item not found' });
|
|
res.json(result.rows[0]);
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
// Delete budget item
|
|
router.delete('/:id', authMiddleware, async (req, res) => {
|
|
try {
|
|
const result = await pool.query(
|
|
`DELETE FROM budget_items WHERE id=$1 AND event_id IN (SELECT id FROM events WHERE user_id=$2) RETURNING id`,
|
|
[req.params.id, req.userId]
|
|
);
|
|
if (result.rows.length === 0) return res.status(404).json({ error: 'Budget item not found' });
|
|
res.json({ message: 'Budget item deleted' });
|
|
} catch (err) {
|
|
console.error(err);
|
|
res.status(500).json({ error: 'Server error' });
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|