diff --git a/api/watch_ticket.php b/api/watch_ticket.php index 40e9867..a9448f5 100644 --- a/api/watch_ticket.php +++ b/api/watch_ticket.php @@ -9,6 +9,7 @@ require_once __DIR__ . '/bootstrap.php'; require_once dirname(__DIR__) . '/models/TicketModel.php'; +require_once dirname(__DIR__) . '/models/AuditLogModel.php'; $data = json_decode(file_get_contents('php://input'), true) ?? []; @@ -43,6 +44,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ); $stmt->bind_param("si", $ticketId, $userId); $stmt->execute(); + $rowsChanged = $stmt->affected_rows; $stmt->close(); } else { $stmt = $conn->prepare( @@ -50,9 +52,17 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ); $stmt->bind_param("si", $ticketId, $userId); $stmt->execute(); + $rowsChanged = $stmt->affected_rows; $stmt->close(); } + // Only log an actual state change — INSERT IGNORE/DELETE are no-ops when + // the user was already watching/not watching, and that shouldn't show up + // in the ticket's timeline as a new event. + if ($rowsChanged > 0) { + (new AuditLogModel($conn))->log($userId, $action, 'ticket', $ticketId); + } + // Return updated state $countStmt = $conn->prepare( "SELECT COUNT(*) as cnt FROM ticket_watchers WHERE ticket_id = ?" diff --git a/models/AuditLogModel.php b/models/AuditLogModel.php index dd691cc..001abcb 100644 --- a/models/AuditLogModel.php +++ b/models/AuditLogModel.php @@ -20,7 +20,8 @@ class AuditLogModel private const VALID_ACTION_TYPES = [ 'create', 'update', 'delete', 'view', 'security_event', 'login', 'logout', 'assign', 'unassign', 'comment', 'mention', - 'revoke', 'attachment_upload', 'attachment_delete', 'bulk_update' + 'revoke', 'attachment_upload', 'attachment_delete', 'bulk_update', + 'watch', 'unwatch' ]; /** @var array Allowed entity types for filtering */ diff --git a/views/TicketView.php b/views/TicketView.php index f84c4fd..3c65875 100644 --- a/views/TicketView.php +++ b/views/TicketView.php @@ -32,6 +32,8 @@ function getEventIcon(string $actionType): string 'status_change' => '[!]', 'attachment' => '[^]', 'delete' => '[x]', + 'watch' => '[o]', + 'unwatch' => '[o]', default => '[*]', }; } @@ -53,6 +55,10 @@ function formatAction(array $event): string return 'uploaded a file'; case 'delete': return 'deleted a comment'; + case 'watch': + return 'started watching this ticket'; + case 'unwatch': + return 'stopped watching this ticket'; case 'assign': if (is_array($det) && isset($det['assigned_to']['to'])) { $to = $det['assigned_to']['to'] ?: 'Unassigned';