{success, ticket, comments} * GET ?status=&priority=&host= -> {success, tickets, page, total, pages} * &page=&limit= */ header('Content-Type: application/json'); error_reporting(E_ALL); ini_set('display_errors', 0); // Rate limiting (same pattern as the other Bearer API endpoints) require_once dirname(__DIR__) . '/middleware/RateLimitMiddleware.php'; RateLimitMiddleware::apply('api'); require_once dirname(__DIR__) . '/config/config.php'; require_once dirname(__DIR__) . '/helpers/Database.php'; require_once dirname(__DIR__) . '/middleware/ApiKeyAuth.php'; require_once dirname(__DIR__) . '/models/TicketModel.php'; require_once dirname(__DIR__) . '/models/CommentModel.php'; try { $conn = Database::getConnection(); } catch (Throwable $e) { error_log('tickets_api: DB connection failed: ' . $e->getMessage()); http_response_code(500); echo json_encode(['success' => false, 'error' => 'Internal server error']); exit; } $apiKeyAuth = new ApiKeyAuth($conn); try { $apiKeyAuth->authenticate(); } catch (Exception $e) { // ApiKeyAuth already sent the 401 response. exit; } // Reads only need the 'read' scope. $apiKeyAuth->requireScope('read'); if ($_SERVER['REQUEST_METHOD'] !== 'GET') { http_response_code(405); echo json_encode(['success' => false, 'error' => 'Method not allowed. Use GET.']); exit; } $ticketModel = new TicketModel($conn); // ── READ ONE ────────────────────────────────────────────────────────────── if (isset($_GET['ticket_id']) && trim((string)$_GET['ticket_id']) !== '') { $ticketId = trim((string)$_GET['ticket_id']); $ticket = $ticketModel->getTicketById($ticketId); if (!$ticket) { http_response_code(404); echo json_encode(['success' => false, 'error' => 'Ticket not found']); exit; } // Flat list of comments (newest first) — same fetch the ticket view uses. $commentModel = new CommentModel($conn); $comments = $commentModel->getCommentsByTicketId($ticketId, false); echo json_encode([ 'success' => true, 'ticket' => $ticket, 'comments' => $comments, ]); exit; } // ── LIST / TRIAGE ─────────────────────────────────────────────────────────── $status = (isset($_GET['status']) && trim((string)$_GET['status']) !== '') ? trim((string)$_GET['status']) : 'Open'; $page = isset($_GET['page']) ? (int)$_GET['page'] : 1; if ($page < 1) { $page = 1; } $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 25; if ($limit < 1) { $limit = 25; } if ($limit > 100) { $limit = 100; // cap } $filters = []; if (isset($_GET['priority']) && trim((string)$_GET['priority']) !== '') { $priority = (int)$_GET['priority']; if ($priority >= 1 && $priority <= 5) { // Exact-priority match via the min/max range filter. $filters['priority_min'] = $priority; $filters['priority_max'] = $priority; } } // hwmon puts the host in the title (e.g. "[hostname] ..."), so a host filter is a // title substring match — served by getAllTickets's `search` param (title search). $search = null; if (isset($_GET['host']) && trim((string)$_GET['host']) !== '') { $search = trim((string)$_GET['host']); } // user = null => getAllTickets skips visibility filtering and returns the full // queue (this is a trusted server credential, not an end user). $result = $ticketModel->getAllTickets( $page, $limit, $status, 'ticket_id', 'desc', null, null, $search, $filters, null ); echo json_encode([ 'success' => true, 'tickets' => $result['tickets'], 'page' => $result['current_page'], 'total' => $result['total'], 'pages' => $result['pages'], ]); exit;