230 lines
8.2 KiB
PHP
230 lines
8.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/../../../../../src/Core/Auth.php';
|
|
require_once __DIR__ . '/../../../../../src/Core/Db.php';
|
|
require_once __DIR__ . '/../../../../../src/Core/TokenManager.php';
|
|
require_once __DIR__ . '/../../../../../src/Modules/Bugtracker/BugRepo.php';
|
|
|
|
use Deploymentcenter\Core\Auth;
|
|
use Deploymentcenter\Core\Db;
|
|
use Deploymentcenter\Core\TokenManager;
|
|
use Deploymentcenter\Modules\Bugtracker\BugRepo;
|
|
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
|
|
try {
|
|
$config = require __DIR__ . '/../../../../../config/config.php';
|
|
$db = Db::connect($config['db']);
|
|
|
|
// Authenticate Request (Session OR Token)
|
|
$isAuthenticated = false;
|
|
$authorName = 'admin';
|
|
|
|
if (Auth::isLoggedIn()) {
|
|
$isAuthenticated = true;
|
|
$authorName = $_SESSION['dc_username'] ?? 'admin';
|
|
} else {
|
|
$headers = getallheaders();
|
|
$token = $headers['X-Agent-Token'] ?? $headers['x-agent-token'] ?? null;
|
|
if (!$token && !empty($headers['Authorization'])) {
|
|
if (preg_match('/Bearer\s+(.+)/i', $headers['Authorization'], $matches)) {
|
|
$token = trim($matches[1]);
|
|
}
|
|
}
|
|
|
|
if ($token) {
|
|
$tokenMgr = new TokenManager($db);
|
|
$tokenInfo = $tokenMgr->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 === 'projects' || str_contains($path, '/projects')) {
|
|
echo json_encode(['status' => 'success', 'projects' => $repo->getProjects()], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
|
exit;
|
|
}
|
|
|
|
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()]);
|
|
}
|