feat: Enhance condo and family data models

Adds new fields for detailed address information and notes to the Condo and Family types.
Updates database schema and server API endpoints to support these new fields, improving data richness for location and specific family/condo details.
This commit is contained in:
2025-12-07 16:10:33 +01:00
parent 28148ee550
commit fd107c1ef8
9 changed files with 422 additions and 294 deletions

View File

@@ -144,22 +144,38 @@ app.get('/api/condos', authenticateToken, async (req, res) => {
try {
const [rows] = await pool.query('SELECT * FROM condos');
res.json(rows.map(r => ({
id: r.id, name: r.name, address: r.address, iban: r.iban, defaultMonthlyQuota: parseFloat(r.default_monthly_quota), image: r.image
id: r.id,
name: r.name,
address: r.address,
streetNumber: r.street_number,
city: r.city,
province: r.province,
zipCode: r.zip_code,
notes: r.notes,
iban: r.iban,
defaultMonthlyQuota: parseFloat(r.default_monthly_quota),
image: r.image
})));
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.post('/api/condos', authenticateToken, requireAdmin, async (req, res) => {
const { name, address, defaultMonthlyQuota } = req.body;
const { name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota } = req.body;
const id = uuidv4();
try {
await pool.query('INSERT INTO condos (id, name, address, default_monthly_quota) VALUES (?, ?, ?, ?)', [id, name, address, defaultMonthlyQuota]);
res.json({ id, name, address, defaultMonthlyQuota });
await pool.query(
'INSERT INTO condos (id, name, address, street_number, city, province, zip_code, notes, default_monthly_quota) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[id, name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota]
);
res.json({ id, name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota });
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.put('/api/condos/:id', authenticateToken, requireAdmin, async (req, res) => {
const { name, address, defaultMonthlyQuota } = req.body;
const { name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota } = req.body;
try {
await pool.query('UPDATE condos SET name = ?, address = ?, default_monthly_quota = ? WHERE id = ?', [name, address, defaultMonthlyQuota, req.params.id]);
await pool.query(
'UPDATE condos SET name = ?, address = ?, street_number = ?, city = ?, province = ?, zip_code = ?, notes = ?, default_monthly_quota = ? WHERE id = ?',
[name, address, streetNumber, city, province, zipCode, notes, defaultMonthlyQuota, req.params.id]
);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -172,32 +188,58 @@ app.delete('/api/condos/:id', authenticateToken, requireAdmin, async (req, res)
// --- FAMILIES ---
app.get('/api/families', authenticateToken, async (req, res) => {
const { condoId } = req.query;
try {
let query = `SELECT f.* FROM families f`;
let params = [];
// Authorization/Filtering logic
if (req.user.role !== 'admin' && req.user.role !== 'poweruser') {
// Regular user: can only see their own family
if (!req.user.familyId) return res.json([]);
query += ' WHERE f.id = ?';
params.push(req.user.familyId);
} else {
// Admin: If condoId provided, filter by it.
if (condoId) {
query += ' WHERE f.condo_id = ?';
params.push(condoId);
}
}
const [rows] = await pool.query(query, params);
res.json(rows.map(r => ({
id: r.id, condoId: r.condo_id, name: r.name, unitNumber: r.unit_number, contactEmail: r.contact_email, customMonthlyQuota: r.custom_monthly_quota ? parseFloat(r.custom_monthly_quota) : undefined, balance: 0
id: r.id,
condoId: r.condo_id,
name: r.name,
unitNumber: r.unit_number,
stair: r.stair,
floor: r.floor,
notes: r.notes,
contactEmail: r.contact_email,
customMonthlyQuota: r.custom_monthly_quota ? parseFloat(r.custom_monthly_quota) : undefined,
balance: 0
})));
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.post('/api/families', authenticateToken, requireAdmin, async (req, res) => {
const { name, unitNumber, contactEmail, condoId, customMonthlyQuota } = req.body;
const { name, unitNumber, stair, floor, notes, contactEmail, condoId, customMonthlyQuota } = req.body;
const id = uuidv4();
try {
await pool.query('INSERT INTO families (id, condo_id, name, unit_number, contact_email, custom_monthly_quota) VALUES (?, ?, ?, ?, ?, ?)', [id, condoId, name, unitNumber, contactEmail, customMonthlyQuota || null]);
res.json({ id, condoId, name, unitNumber, contactEmail, customMonthlyQuota });
await pool.query(
'INSERT INTO families (id, condo_id, name, unit_number, stair, floor, notes, contact_email, custom_monthly_quota) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)',
[id, condoId, name, unitNumber, stair, floor, notes, contactEmail, customMonthlyQuota || null]
);
res.json({ id, condoId, name, unitNumber, stair, floor, notes, contactEmail, customMonthlyQuota });
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.put('/api/families/:id', authenticateToken, requireAdmin, async (req, res) => {
const { name, unitNumber, contactEmail, customMonthlyQuota } = req.body;
const { name, unitNumber, stair, floor, notes, contactEmail, customMonthlyQuota } = req.body;
try {
await pool.query('UPDATE families SET name = ?, unit_number = ?, contact_email = ?, custom_monthly_quota = ? WHERE id = ?', [name, unitNumber, contactEmail, customMonthlyQuota || null, req.params.id]);
await pool.query(
'UPDATE families SET name = ?, unit_number = ?, stair = ?, floor = ?, notes = ?, contact_email = ?, custom_monthly_quota = ? WHERE id = ?',
[name, unitNumber, stair, floor, notes, contactEmail, customMonthlyQuota || null, req.params.id]
);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -305,8 +347,19 @@ app.post('/api/payments', authenticateToken, async (req, res) => {
// --- USERS ---
app.get('/api/users', authenticateToken, requireAdmin, async (req, res) => {
const { condoId } = req.query;
try {
const [rows] = await pool.query('SELECT id, email, name, role, phone, family_id, receive_alerts FROM users');
let query = 'SELECT u.id, u.email, u.name, u.role, u.phone, u.family_id, u.receive_alerts FROM users u';
let params = [];
// Filter users by condo.
// Logic: Users belong to families, families belong to condos.
if (condoId) {
query += ' LEFT JOIN families f ON u.family_id = f.id WHERE f.condo_id = ?';
params.push(condoId);
}
const [rows] = await pool.query(query, params);
res.json(rows.map(r => ({ id: r.id, email: r.email, name: r.name, role: r.role, phone: r.phone, familyId: r.family_id, receiveAlerts: !!r.receive_alerts })));
} catch (e) { res.status(500).json({ error: e.message }); }
});
@@ -344,17 +397,24 @@ app.delete('/api/users/:id', authenticateToken, requireAdmin, async (req, res) =
// --- ALERTS ---
app.get('/api/alerts', authenticateToken, requireAdmin, async (req, res) => {
const { condoId } = req.query;
try {
const [rows] = await pool.query('SELECT * FROM alerts');
res.json(rows.map(r => ({ id: r.id, subject: r.subject, body: r.body, daysOffset: r.days_offset, offsetType: r.offset_type, sendHour: r.send_hour, active: !!r.active, lastSent: r.last_sent })));
let query = 'SELECT * FROM alerts';
let params = [];
if (condoId) {
query += ' WHERE condo_id = ?';
params.push(condoId);
}
const [rows] = await pool.query(query, params);
res.json(rows.map(r => ({ id: r.id, condoId: r.condo_id, subject: r.subject, body: r.body, daysOffset: r.days_offset, offsetType: r.offset_type, sendHour: r.send_hour, active: !!r.active, lastSent: r.last_sent })));
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.post('/api/alerts', authenticateToken, requireAdmin, async (req, res) => {
const { subject, body, daysOffset, offsetType, sendHour, active } = req.body;
const { condoId, subject, body, daysOffset, offsetType, sendHour, active } = req.body;
const id = uuidv4();
try {
await pool.query('INSERT INTO alerts (id, subject, body, days_offset, offset_type, send_hour, active) VALUES (?, ?, ?, ?, ?, ?, ?)', [id, subject, body, daysOffset, offsetType, sendHour, active]);
res.json({ id, subject, body, daysOffset, offsetType, sendHour, active });
await pool.query('INSERT INTO alerts (id, condo_id, subject, body, days_offset, offset_type, send_hour, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', [id, condoId, subject, body, daysOffset, offsetType, sendHour, active]);
res.json({ id, condoId, subject, body, daysOffset, offsetType, sendHour, active });
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.put('/api/alerts/:id', authenticateToken, requireAdmin, async (req, res) => {