feat: Add targeted notices to Condopay
Implements the ability to send notices to specific families within a condominium, rather than broadcasting to all. This includes: - Updating the `Notice` type with `targetFamilyIds`. - Adding a `target_families` JSON column to the `notices` table in the database, with a migration for existing installations. - Modifying the API to handle the new `targetFamilyIds` field during notice creation and retrieval. - Updating the UI to allow users to select specific families for notices.
This commit is contained in:
@@ -297,24 +297,45 @@ app.get('/api/notices', authenticateToken, async (req, res) => {
|
||||
}
|
||||
query += ' ORDER BY date DESC';
|
||||
const [rows] = await pool.query(query, params);
|
||||
res.json(rows.map(r => ({ id: r.id, condoId: r.condo_id, title: r.title, content: r.content, type: r.type, link: r.link, date: r.date, active: !!r.active })));
|
||||
res.json(rows.map(r => ({
|
||||
id: r.id,
|
||||
condoId: r.condo_id,
|
||||
title: r.title,
|
||||
content: r.content,
|
||||
type: r.type,
|
||||
link: r.link,
|
||||
date: r.date,
|
||||
active: !!r.active,
|
||||
targetFamilyIds: r.target_families ? (typeof r.target_families === 'string' ? JSON.parse(r.target_families) : r.target_families) : []
|
||||
})));
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
app.post('/api/notices', authenticateToken, requireAdmin, async (req, res) => {
|
||||
const { condoId, title, content, type, link, active } = req.body;
|
||||
const { condoId, title, content, type, link, active, targetFamilyIds } = req.body;
|
||||
const id = uuidv4();
|
||||
try {
|
||||
await pool.query('INSERT INTO notices (id, condo_id, title, content, type, link, active, date) VALUES (?, ?, ?, ?, ?, ?, ?, NOW())', [id, condoId, title, content, type, link, active]);
|
||||
res.json({ id, condoId, title, content, type, link, active });
|
||||
const targetFamiliesJson = targetFamilyIds && targetFamilyIds.length > 0 ? JSON.stringify(targetFamilyIds) : null;
|
||||
await pool.query(
|
||||
'INSERT INTO notices (id, condo_id, title, content, type, link, active, target_families, date) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())',
|
||||
[id, condoId, title, content, type, link, active, targetFamiliesJson]
|
||||
);
|
||||
res.json({ id, condoId, title, content, type, link, active, targetFamilyIds });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
app.put('/api/notices/:id', authenticateToken, requireAdmin, async (req, res) => {
|
||||
const { title, content, type, link, active } = req.body;
|
||||
const { title, content, type, link, active, targetFamilyIds } = req.body;
|
||||
try {
|
||||
await pool.query('UPDATE notices SET title = ?, content = ?, type = ?, link = ?, active = ? WHERE id = ?', [title, content, type, link, active, req.params.id]);
|
||||
const targetFamiliesJson = targetFamilyIds && targetFamilyIds.length > 0 ? JSON.stringify(targetFamilyIds) : null;
|
||||
await pool.query(
|
||||
'UPDATE notices SET title = ?, content = ?, type = ?, link = ?, active = ?, target_families = ? WHERE id = ?',
|
||||
[title, content, type, link, active, targetFamiliesJson, req.params.id]
|
||||
);
|
||||
res.json({ success: true });
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
app.delete('/api/notices/:id', authenticateToken, requireAdmin, async (req, res) => {
|
||||
try {
|
||||
await pool.query('DELETE FROM notices WHERE id = ?', [req.params.id]);
|
||||
@@ -337,13 +358,40 @@ app.get('/api/notices/:id/reads', authenticateToken, async (req, res) => {
|
||||
app.get('/api/notices/unread', authenticateToken, async (req, res) => {
|
||||
const { userId, condoId } = req.query;
|
||||
try {
|
||||
// First get user's family ID to filter targeted notices
|
||||
const [users] = await pool.query('SELECT family_id FROM users WHERE id = ?', [userId]);
|
||||
const userFamilyId = users.length > 0 ? users[0].family_id : null;
|
||||
|
||||
const [rows] = await pool.query(`
|
||||
SELECT n.* FROM notices n
|
||||
LEFT JOIN notice_reads nr ON n.id = nr.notice_id AND nr.user_id = ?
|
||||
WHERE n.condo_id = ? AND n.active = TRUE AND nr.read_at IS NULL
|
||||
ORDER BY n.date DESC
|
||||
`, [userId, condoId]);
|
||||
res.json(rows.map(r => ({ id: r.id, condoId: r.condo_id, title: r.title, content: r.content, type: r.type, link: r.link, date: r.date, active: !!r.active })));
|
||||
|
||||
// Filter in JS for simplicity across DBs (handling JSON field logic)
|
||||
const filtered = rows.filter(n => {
|
||||
if (!n.target_families) return true; // Public to all
|
||||
let targets = n.target_families;
|
||||
if (typeof targets === 'string') {
|
||||
try { targets = JSON.parse(targets); } catch(e) { return true; }
|
||||
}
|
||||
if (!Array.isArray(targets) || targets.length === 0) return true; // Empty array = Public
|
||||
|
||||
// If explicit targets are set, user MUST belong to one of the families
|
||||
return userFamilyId && targets.includes(userFamilyId);
|
||||
});
|
||||
|
||||
res.json(filtered.map(r => ({
|
||||
id: r.id,
|
||||
condoId: r.condo_id,
|
||||
title: r.title,
|
||||
content: r.content,
|
||||
type: r.type,
|
||||
link: r.link,
|
||||
date: r.date,
|
||||
active: !!r.active
|
||||
})));
|
||||
} catch (e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user