validateToken($token, 'bugtracker:manage'); if ($tokenInfo) { $isAuthenticated = true; $authorName = 'agent:' . ($tokenInfo['name'] ?? $tokenInfo['token_id']); } } } if (!$isAuthenticated) { http_response_code(401); echo json_encode(['status' => 'error', 'message' => 'Unauthorized: Valid Session or Bearer Token with scope bugtracker:manage required']); exit; } $repo = new BugRepo($db); $uri = $_SERVER['REQUEST_URI']; $method = $_SERVER['REQUEST_METHOD']; $rawInput = file_get_contents('php://input'); $input = json_decode($rawInput, true) ?: $_POST; // Parse sub-route if any $path = parse_url($uri, PHP_URL_PATH); $action = $_GET['action'] ?? null; // Handle Item Detail/Comment/Resolve via ID in URL or query params $itemId = isset($_GET['id']) ? (int)$_GET['id'] : 0; if (!$itemId && preg_match('/\/manage\/items\/(\d+)/', $path, $m)) { $itemId = (int)$m[1]; } // Sub-actions if ($action === 'stats' || str_ends_with($path, '/stats')) { echo json_encode(['status' => 'success', 'stats' => $repo->getStats()], JSON_PRETTY_PRINT); exit; } if ($action === 'resolve' || str_contains($path, '/resolve')) { if ($method !== 'POST') { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'POST required for resolve']); exit; } if (!$itemId) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Missing item ID']); exit; } $build = !empty($input['resolved_in_build']) ? trim($input['resolved_in_build']) : 'v1.0.0'; $notes = !empty($input['resolution_notes']) ? trim($input['resolution_notes']) : null; $author = !empty($input['author']) ? trim($input['author']) : $authorName; $ok = $repo->resolveItem($itemId, $build, $notes, $author); if ($ok) { echo json_encode(['status' => 'success', 'message' => "Item #{$itemId} resolved in build {$build}"]); } else { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Failed to resolve item']); } exit; } if ($action === 'comment' || str_contains($path, '/comments')) { if ($method !== 'POST') { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'POST required for comment']); exit; } if (!$itemId) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Missing item ID']); exit; } $comment = !empty($input['comment']) ? trim($input['comment']) : ''; if (empty($comment)) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Comment cannot be empty']); exit; } $author = !empty($input['author']) ? trim($input['author']) : $authorName; $actionTaken = !empty($input['action_taken']) ? trim($input['action_taken']) : 'commented'; $meta = isset($input['meta']) && is_array($input['meta']) ? $input['meta'] : null; $comm = $repo->addComment($itemId, $author, $comment, $actionTaken, $meta); echo json_encode(['status' => 'success', 'comment' => $comm]); exit; } if ($action === 'status' || str_contains($path, '/status')) { if ($method !== 'POST') { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'POST required for status change']); exit; } if (!$itemId) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Missing item ID']); exit; } $status = !empty($input['status']) ? trim($input['status']) : 'open'; $notes = !empty($input['notes']) ? trim($input['notes']) : null; $author = !empty($input['author']) ? trim($input['author']) : $authorName; $ok = $repo->updateStatus($itemId, $status, $notes, $author); if ($ok) { echo json_encode(['status' => 'success', 'message' => "Status for #{$itemId} updated to {$status}"]); } else { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Invalid status']); } exit; } if ($action === 'update' || str_contains($path, '/update')) { if ($method !== 'POST') { http_response_code(405); echo json_encode(['status' => 'error', 'message' => 'POST required for update']); exit; } if (!$itemId && !empty($input['id'])) { $itemId = (int)$input['id']; } if (!$itemId) { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Missing item ID']); exit; } $author = !empty($input['author']) ? trim($input['author']) : $authorName; $ok = $repo->updateItemDetails($itemId, $input, $author); if ($ok) { echo json_encode(['status' => 'success', 'message' => "Item #{$itemId} updated successfully"]); } else { http_response_code(400); echo json_encode(['status' => 'error', 'message' => 'Failed to update item']); } exit; } // Detail View of a single item if ($itemId > 0 && $method === 'GET') { $details = $repo->getItemDetails($itemId); if (!$details) { http_response_code(404); echo json_encode(['status' => 'error', 'message' => 'Item not found']); exit; } echo json_encode(['status' => 'success', 'item' => $details], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); exit; } // Default: List Items $filters = [ 'project_slug' => $_GET['project_slug'] ?? $_GET['project'] ?? 'all', 'environment' => $_GET['environment'] ?? $_GET['env'] ?? 'all', 'type' => $_GET['type'] ?? 'all', 'status' => $_GET['status'] ?? 'all', 'severity' => $_GET['severity'] ?? 'all', 'push_id' => $_GET['push_id'] ?? '', 'target_agent' => $_GET['target_agent'] ?? $_GET['agent'] ?? '', 'search' => $_GET['search'] ?? $_GET['q'] ?? '', ]; $items = $repo->getItems($filters); echo json_encode([ 'status' => 'success', 'count' => count($items), 'filters' => $filters, 'items' => $items, ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } catch (Throwable $t) { http_response_code(500); echo json_encode(['status' => 'error', 'message' => 'Manage API Error: ' . $t->getMessage()]); }