feat: Implement ticket commenting functionality

Adds the ability for users to comment on tickets, view comments, and distinguish between user and admin responses. Also introduces a new 'SUSPENDED' status for tickets and refactors database schema and API endpoints to support comments.
This commit is contained in:
2025-12-09 15:58:52 +01:00
parent 4107051585
commit a97dcfa33e
9 changed files with 324 additions and 151 deletions

View File

@@ -617,6 +617,81 @@ app.get('/api/tickets/:id/attachments/:attachmentId', authenticateToken, async (
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.get('/api/tickets/:id/comments', authenticateToken, async (req, res) => {
try {
const [rows] = await pool.query(`
SELECT c.*, u.name as user_name, u.role as user_role
FROM ticket_comments c
JOIN users u ON c.user_id = u.id
WHERE c.ticket_id = ?
ORDER BY c.created_at ASC
`, [req.params.id]);
res.json(rows.map(r => ({
id: r.id,
ticketId: r.ticket_id,
userId: r.user_id,
userName: r.user_name,
text: r.text,
createdAt: r.created_at,
isAdminResponse: r.user_role === 'admin' || r.user_role === 'poweruser'
})));
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.post('/api/tickets/:id/comments', authenticateToken, async (req, res) => {
const { text } = req.body;
const userId = req.user.id;
const ticketId = req.params.id;
const commentId = uuidv4();
const isAdmin = req.user.role === 'admin' || req.user.role === 'poweruser';
try {
await pool.query(
'INSERT INTO ticket_comments (id, ticket_id, user_id, text) VALUES (?, ?, ?, ?)',
[commentId, ticketId, userId, text]
);
// --- EMAIL NOTIFICATION LOGIC ---
// 1. Get ticket info to know who to notify
const [ticketRows] = await pool.query(`
SELECT t.title, t.user_id, u.email as creator_email, u.receive_alerts as creator_alerts
FROM tickets t
JOIN users u ON t.user_id = u.id
WHERE t.id = ?
`, [ticketId]);
if (ticketRows.length > 0) {
const ticket = ticketRows[0];
const subject = `Nuovo commento sul ticket: ${ticket.title}`;
// If ADMIN replied -> Notify Creator
if (isAdmin && ticket.creator_email && ticket.creator_alerts) {
const body = `Salve,\n\nÈ stato aggiunto un nuovo commento al tuo ticket "${ticket.title}".\n\nCommento:\n${text}\n\nAccedi alla piattaforma per rispondere.`;
sendDirectEmail(ticket.creator_email, subject, body);
}
// If CREATOR replied -> Notify Admins (logic similar to new ticket)
else if (!isAdmin) {
const [admins] = await pool.query(`
SELECT u.email FROM users u
LEFT JOIN families f ON u.family_id = f.id
JOIN tickets t ON t.id = ?
WHERE (u.role = 'admin' OR u.role = 'poweruser')
AND (f.condo_id = t.condo_id OR u.family_id IS NULL)
AND u.receive_alerts = TRUE
`, [ticketId]);
const body = `Salve,\n\nNuova risposta dall'utente sul ticket "${ticket.title}".\n\nCommento:\n${text}\n\nAccedi alla piattaforma per gestire.`;
for(const admin of admins) {
if (admin.email) sendDirectEmail(admin.email, subject, body);
}
}
}
res.json({ success: true, id: commentId });
} catch (e) { res.status(500).json({ error: e.message }); }
});
app.post('/api/tickets', authenticateToken, async (req, res) => {
const { condoId, title, description, category, priority, attachments } = req.body;
const userId = req.user.id;
@@ -709,23 +784,32 @@ app.put('/api/tickets/:id', authenticateToken, async (req, res) => {
app.delete('/api/tickets/:id', authenticateToken, async (req, res) => {
// Only delete own ticket if open, or admin can delete any
// MODIFIED: Prevent deletion if status is CLOSED or RESOLVED (Archived)
const isAdmin = req.user.role === 'admin' || req.user.role === 'poweruser';
const userId = req.user.id;
try {
// Check status first
const [rows] = await pool.query('SELECT status, user_id FROM tickets WHERE id = ?', [req.params.id]);
if (rows.length === 0) return res.status(404).json({ message: 'Ticket not found' });
const ticket = rows[0];
// Block deletion of Archived tickets
if (ticket.status === 'CLOSED' || ticket.status === 'RESOLVED') {
return res.status(403).json({ message: 'Cannot delete archived tickets. They are kept for history.' });
}
let query = 'DELETE FROM tickets WHERE id = ?';
let params = [req.params.id];
if (!isAdmin) {
query += ' AND user_id = ? AND status = "OPEN"'; // Users can only delete their own OPEN tickets
params.push(userId);
}
const [result] = await pool.query(query, params);
if (result.affectedRows === 0) {
return res.status(403).json({ message: 'Cannot delete ticket (Permission denied or not found)' });
// Additional check for user ownership
if (ticket.user_id !== userId) return res.status(403).json({ message: 'Forbidden' });
if (ticket.status !== 'OPEN') return res.status(403).json({ message: 'Can only delete OPEN tickets' });
}
await pool.query(query, params);
res.json({ success: true });
} catch (e) { res.status(500).json({ error: e.message }); }
});