exec("ALTER TABLE network_nodes ADD COLUMN notes TEXT DEFAULT ''"); } catch (Exception $e) {} // Auth check (except for session check) $path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH); $path = str_replace('/api/', '', $path); $segments = explode('/', trim($path, '/')); $resource = $segments[0] ?? ''; if ($resource !== 'session' && $resource !== 'login' && $resource !== 'logout' && $resource !== 'registration') { $loggedin = isset($_SESSION['neptune_loggedin']) && $_SESSION['neptune_loggedin'] === true; if (!$loggedin) { http_response_code(401); echo json_encode(['error' => 'Unauthorized']); exit; } } $method = $_SERVER['REQUEST_METHOD']; $id = $segments[1] ?? null; try { switch ($resource) { case 'session': handleSession($method, $db); break; case 'settings': handleSettings($method, $db); break; case 'login': handleLogin($method, $db); break; case 'logout': handleLogout(); break; case 'teams': handleTeams($method, $id, $db); break; case 'events': handleEvents($method, $id, $db); break; case 'comments': handleComments($method, $id, $db); break; case 'nodes': handleNodes($method, $id, $db); break; case 'links': handleLinks($method, $id, $db); break; case 'documents': handleDocuments($method, $id, $db); break; case 'shapes': handleShapes($method, $id, $db); break; case 'registration': handleRegistration($method, $db); break; case 'attachments': handleAttachments($method, $id, $db); break; case 'tags': if ($method === 'GET') { echo json_encode($db->query("SELECT DISTINCT tag FROM event_tags ORDER BY tag")->fetchAll(PDO::FETCH_COLUMN)); } else { http_response_code(405); } break; default: http_response_code(404); echo json_encode(['error' => 'Not found']); } } catch (Exception $e) { http_response_code(500); echo json_encode(['error' => $e->getMessage()]); } function handleRegistration($method, $db) { if ($method === 'GET') { $stmt = $db->prepare("SELECT setting_value FROM neptune_settings WHERE setting_key = 'registration_enabled'"); $stmt->execute(); $row = $stmt->fetch(); $enabled = $row ? $row['setting_value'] !== '0' : true; echo json_encode(['registration_enabled' => $enabled]); } elseif ($method === 'POST') { $role = $_SESSION['neptune_role'] ?? 'user'; if ($role !== 'admin') { http_response_code(403); echo json_encode(['error' => 'Admins only']); return; } $data = json_decode(file_get_contents('php://input'), true); $enabled = isset($data['registration_enabled']) ? ($data['registration_enabled'] ? '1' : '0') : '1'; $stmt = $db->prepare("INSERT INTO neptune_settings (setting_key, setting_value) VALUES ('registration_enabled', ?) ON DUPLICATE KEY UPDATE setting_value = ?"); $stmt->execute([$enabled, $enabled]); echo json_encode(['status' => 'success', 'registration_enabled' => $enabled === '1']); } } function handleSession($method, $db) { $loggedin = isset($_SESSION['neptune_loggedin']) && $_SESSION['neptune_loggedin'] === true; if (!$loggedin && $method === 'GET') { echo json_encode(['loggedin' => false]); return; } if ($loggedin) { $role = $_SESSION['neptune_role'] ?? 'user'; $stmt = $db->prepare("SELECT COUNT(*) as c FROM neptune_users WHERE role='admin'"); $stmt->execute(); $adminCount = $stmt->fetch()['c']; echo json_encode([ 'loggedin' => true, 'username' => $_SESSION['neptune_username'] ?? 'Unknown', 'role' => $role, 'admin_count' => (int)$adminCount ]); } else { echo json_encode(['loggedin' => false]); } } function handleLogin($method, $db) { if ($method !== 'POST') { http_response_code(405); echo json_encode(['error' => 'POST required']); return; } $data = json_decode(file_get_contents('php://input'), true); $auth_token = $data['auth_token'] ?? ''; if (!$auth_token) { http_response_code(400); echo json_encode(['error' => 'auth_token required']); return; } $check_url = "https://auth.jakach.ch/api/auth/check_auth_key.php?auth_token=" . urlencode($auth_token); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $check_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 15); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http_code !== 200 || !$response) { http_response_code(502); echo json_encode(['error' => 'Failed to contact auth server']); return; } $info = json_decode($response, true); if (!isset($info['status']) || $info['status'] !== 'success') { http_response_code(401); echo json_encode(['error' => $info['msg'] ?? 'Auth failed']); return; } $user_token = $info['user_token']; $username = $info['username']; $email = $info['email'] ?? ''; $stmt = $db->prepare("SELECT * FROM neptune_users WHERE user_token = ?"); $stmt->execute([$user_token]); $user = $stmt->fetch(); if ($user) { $_SESSION['neptune_loggedin'] = true; $_SESSION['neptune_user_token'] = $user['user_token']; $_SESSION['neptune_username'] = $user['username']; $_SESSION['neptune_role'] = $user['role']; } else { $stmt = $db->prepare("SELECT setting_value FROM neptune_settings WHERE setting_key = 'registration_enabled'"); $stmt->execute(); $regSetting = $stmt->fetch(); $registrationEnabled = $regSetting ? $regSetting['setting_value'] !== '0' : true; if (!$registrationEnabled) { http_response_code(403); echo json_encode(['error' => 'Registration is disabled by admin']); return; } $count = $db->query("SELECT COUNT(*) as c FROM neptune_users")->fetch()['c']; $role = ($count == 0) ? 'admin' : 'user'; $stmt = $db->prepare("INSERT INTO neptune_users (user_token, username, email, role) VALUES (?, ?, ?, ?)"); $stmt->execute([$user_token, $username, $email, $role]); $_SESSION['neptune_loggedin'] = true; $_SESSION['neptune_user_token'] = $user_token; $_SESSION['neptune_username'] = $username; $_SESSION['neptune_role'] = $role; } echo json_encode([ 'status' => 'success', 'username' => $_SESSION['neptune_username'], 'role' => $_SESSION['neptune_role'] ]); } function handleLogout() { $_SESSION = array(); session_destroy(); echo json_encode(['status' => 'success']); } function handleSettings($method, $db) { $role = $_SESSION['neptune_role'] ?? 'user'; if ($method === 'GET') { if ($role !== 'admin') { http_response_code(403); echo json_encode(['error' => 'Admins only']); return; } $users = $db->query("SELECT id, username, user_token, email, role, created_at FROM neptune_users ORDER BY created_at ASC")->fetchAll(); echo json_encode($users); } elseif ($method === 'POST') { if ($role !== 'admin') { http_response_code(403); echo json_encode(['error' => 'Admins only']); return; } $data = json_decode(file_get_contents('php://input'), true); $user_token = $data['user_token'] ?? ''; if (!$user_token) { http_response_code(400); echo json_encode(['error' => 'user_token required']); return; } // Validate the token with Jakach Auth $check_url = "https://auth.jakach.ch/api/auth/check_auth_key.php?auth_token=" . urlencode($user_token); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $check_url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_TIMEOUT, 10); $response = curl_exec($ch); $http = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($http !== 200 || !$response) { http_response_code(400); echo json_encode(['error' => 'Failed to validate token']); return; } $info = json_decode($response, true); if (!isset($info['status']) || $info['status'] !== 'success') { http_response_code(400); echo json_encode(['error' => 'Invalid token']); return; } $stmt = $db->prepare("SELECT COUNT(*) as c FROM neptune_users WHERE user_token = ?"); $stmt->execute([$user_token]); if ($stmt->fetch()['c'] > 0) { http_response_code(400); echo json_encode(['error' => 'User already exists']); return; } $stmt = $db->prepare("INSERT INTO neptune_users (user_token, username, email, role) VALUES (?, ?, ?, 'user')"); $stmt->execute([$user_token, $info['username'], $info['email'] ?? '']); echo json_encode(['status' => 'success', 'msg' => 'User added']); } elseif ($method === 'DELETE') { if ($role !== 'admin') { http_response_code(403); echo json_encode(['error' => 'Admins only']); return; } $data = json_decode(file_get_contents('php://input'), true); $id = $data['id'] ?? null; if (!$id) { http_response_code(400); echo json_encode(['error' => 'id required']); return; } // Prevent deleting the last admin $stmt = $db->prepare("SELECT role FROM neptune_users WHERE id = ?"); $stmt->execute([$id]); $user = $stmt->fetch(); if ($user && $user['role'] === 'admin') { $adminCount = $db->query("SELECT COUNT(*) as c FROM neptune_users WHERE role='admin'")->fetch()['c']; if ($adminCount <= 1) { http_response_code(400); echo json_encode(['error' => 'Cannot delete the last admin']); return; } } $db->prepare("DELETE FROM neptune_users WHERE id = ?")->execute([$id]); echo json_encode(['status' => 'success']); } } function handleTeams($method, $id, $db) { switch ($method) { case 'GET': if ($id) { $stmt = $db->prepare("SELECT * FROM teams WHERE id = ?"); $stmt->execute([$id]); echo json_encode($stmt->fetch(PDO::FETCH_ASSOC)); } else { echo json_encode($db->query("SELECT * FROM teams ORDER BY name")->fetchAll(PDO::FETCH_ASSOC)); } break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO teams (name, color) VALUES (?, ?)"); $stmt->execute([$data['name'], $data['color'] ?? '#0d6efd']); echo json_encode(['id' => $db->lastInsertId()]); break; } } function loadEventTags($eventId, $db) { $tstmt = $db->prepare("SELECT tag FROM event_tags WHERE event_id = ? ORDER BY tag"); $tstmt->execute([$eventId]); return array_column($tstmt->fetchAll(PDO::FETCH_ASSOC), 'tag'); } function saveEventTags($eventId, $tags, $db) { $db->prepare("DELETE FROM event_tags WHERE event_id = ?")->execute([$eventId]); if (!empty($tags)) { $istmt = $db->prepare("INSERT IGNORE INTO event_tags (event_id, tag) VALUES (?, ?)"); foreach ($tags as $tag) { $tag = trim($tag); if ($tag !== '') $istmt->execute([$eventId, $tag]); } } } function handleEvents($method, $id, $db) { switch ($method) { case 'GET': if ($id) { $stmt = $db->prepare(" SELECT e.*, t.name AS team_name, t.color AS team_color FROM events e JOIN teams t ON e.team_id = t.id WHERE e.id = ? "); $stmt->execute([$id]); $event = $stmt->fetch(PDO::FETCH_ASSOC); if ($event) { $cstmt = $db->prepare("SELECT * FROM comments WHERE event_id = ? ORDER BY created_at ASC"); $cstmt->execute([$id]); $event['comments'] = $cstmt->fetchAll(PDO::FETCH_ASSOC); $event['tags'] = loadEventTags($id, $db); } echo json_encode($event); } else { $teamFilter = $_GET['team_id'] ?? null; $tagFilter = $_GET['tag'] ?? null; $sql = " SELECT e.*, t.name AS team_name, t.color AS team_color FROM events e JOIN teams t ON e.team_id = t.id "; $params = []; $conditions = []; if ($teamFilter) { $conditions[] = "e.team_id = ?"; $params[] = $teamFilter; } if ($tagFilter) { $sql .= " JOIN event_tags et ON e.id = et.event_id"; $conditions[] = "et.tag = ?"; $params[] = $tagFilter; } if ($conditions) $sql .= " WHERE " . implode(' AND ', $conditions); $sql .= " ORDER BY e.occurred_at DESC"; $stmt = $db->prepare($sql); $stmt->execute($params); $events = $stmt->fetchAll(PDO::FETCH_ASSOC); foreach ($events as &$event) { $cstmt = $db->prepare("SELECT * FROM comments WHERE event_id = ? ORDER BY created_at ASC"); $cstmt->execute([$event['id']]); $event['comments'] = $cstmt->fetchAll(PDO::FETCH_ASSOC); $event['tags'] = loadEventTags($event['id'], $db); } echo json_encode($events); } break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare(" INSERT INTO events (team_id, title, description, severity, event_type, occurred_at) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['team_id'], $data['title'], $data['description'] ?? '', $data['severity'] ?? 'info', $data['event_type'] ?? 'general', $data['occurred_at'] ?? date('Y-m-d H:i:s') ]); $eventId = $db->lastInsertId(); if (isset($data['tags']) && is_array($data['tags'])) { saveEventTags($eventId, $data['tags'], $db); } echo json_encode(['id' => $eventId]); break; case 'PUT': if ($id) { $data = json_decode(file_get_contents('php://input'), true); $fields = []; $params = []; foreach (['team_id','title','description','severity','event_type','occurred_at'] as $f) { if (isset($data[$f])) { $fields[] = "$f = ?"; $params[] = $data[$f]; } } if ($fields) { $params[] = $id; $stmt = $db->prepare("UPDATE events SET " . implode(', ', $fields) . " WHERE id = ?"); $stmt->execute($params); } if (isset($data['tags']) && is_array($data['tags'])) { saveEventTags($id, $data['tags'], $db); } echo json_encode(['updated' => true]); } break; case 'DELETE': if ($id) { // Delete attachments from disk $stmt = $db->prepare("SELECT stored_name FROM file_attachments WHERE event_id = ?"); $stmt->execute([$id]); foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $att) { $path = '/var/www/uploads/' . $att['stored_name']; if (file_exists($path)) unlink($path); } $stmt = $db->prepare("DELETE FROM events WHERE id = ?"); $stmt->execute([$id]); echo json_encode(['deleted' => true]); } break; } } function handleComments($method, $id, $db) { switch ($method) { case 'GET': $eventId = $_GET['event_id'] ?? null; if ($eventId) { $stmt = $db->prepare("SELECT * FROM comments WHERE event_id = ? ORDER BY created_at ASC"); $stmt->execute([$eventId]); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); } break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare("INSERT INTO comments (event_id, author, body) VALUES (?, ?, ?)"); $stmt->execute([$data['event_id'], $data['author'], $data['body']]); echo json_encode(['id' => $db->lastInsertId()]); break; } } function handleNodes($method, $id, $db) { switch ($method) { case 'GET': echo json_encode($db->query("SELECT * FROM network_nodes ORDER BY group_name, label")->fetchAll(PDO::FETCH_ASSOC)); break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare(" INSERT INTO network_nodes (label, ip_address, node_type, status, group_name, notes, pos_x, pos_y) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['label'], $data['ip_address'] ?? '', $data['node_type'] ?? 'host', $data['status'] ?? 'unknown', $data['group_name'] ?? 'default', $data['notes'] ?? '', $data['pos_x'] ?? 0, $data['pos_y'] ?? 0 ]); echo json_encode(['id' => $db->lastInsertId()]); break; case 'PUT': if ($id) { $data = json_decode(file_get_contents('php://input'), true); $fields = []; $params = []; foreach (['label','ip_address','node_type','status','group_name','notes','pos_x','pos_y'] as $f) { if (isset($data[$f])) { $fields[] = "$f = ?"; $params[] = $data[$f]; } } if ($fields) { $params[] = $id; $stmt = $db->prepare("UPDATE network_nodes SET " . implode(', ', $fields) . " WHERE id = ?"); $stmt->execute($params); } echo json_encode(['updated' => true]); } break; case 'DELETE': if ($id) { $db->prepare("DELETE FROM network_nodes WHERE id = ?")->execute([$id]); echo json_encode(['deleted' => true]); } break; } } function handleLinks($method, $id, $db) { switch ($method) { case 'GET': echo json_encode($db->query(" SELECT l.*, s.label AS source_label, s.node_type AS source_type, t.label AS target_label, t.node_type AS target_type FROM network_links l JOIN network_nodes s ON l.source_id = s.id JOIN network_nodes t ON l.target_id = t.id ")->fetchAll(PDO::FETCH_ASSOC)); break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare(" INSERT INTO network_links (source_id, target_id, link_type, label) VALUES (?, ?, ?, ?) "); $stmt->execute([ $data['source_id'], $data['target_id'], $data['link_type'] ?? 'direct', $data['label'] ?? '' ]); echo json_encode(['id' => $db->lastInsertId()]); break; case 'DELETE': if ($id) { $db->prepare("DELETE FROM network_links WHERE id = ?")->execute([$id]); echo json_encode(['deleted' => true]); } break; } } function handleDocuments($method, $id, $db) { $username = $_SESSION['neptune_username'] ?? 'Unknown'; switch ($method) { case 'GET': if ($id) { $stmt = $db->prepare(" SELECT d.*, t.name AS team_name, t.color AS team_color FROM documents d JOIN teams t ON d.team_id = t.id WHERE d.id = ? "); $stmt->execute([$id]); echo json_encode($stmt->fetch(PDO::FETCH_ASSOC)); } else { $teamFilter = $_GET['team_id'] ?? null; $typeFilter = $_GET['doc_type'] ?? null; $sql = " SELECT d.*, t.name AS team_name, t.color AS team_color FROM documents d JOIN teams t ON d.team_id = t.id "; $params = []; $conditions = []; if ($teamFilter) { $conditions[] = "d.team_id = ?"; $params[] = $teamFilter; } if ($typeFilter) { $conditions[] = "d.doc_type = ?"; $params[] = $typeFilter; } if ($conditions) $sql .= " WHERE " . implode(' AND ', $conditions); $sql .= " ORDER BY d.occurred_at DESC"; $stmt = $db->prepare($sql); $stmt->execute($params); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); } break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare(" INSERT INTO documents (doc_type, team_id, title, content, occurred_at) VALUES (?, ?, ?, ?, ?) "); $stmt->execute([ $data['doc_type'], $data['team_id'], $data['title'], $data['content'] ?? '', $data['occurred_at'] ?? date('Y-m-d H:i:s') ]); $docId = $db->lastInsertId(); // Also create a timeline event for this document $teamId = $data['team_id']; $docType = $data['doc_type']; $typeLabels = ['deployment' => 'Deployment', 'attack' => 'Attack', 'incident-report' => 'Incident Report', 'remediation' => 'Remediation', 'exercise' => 'Exercise']; $typeLabel = $typeLabels[$docType] ?? ucfirst($docType); $eventTitle = $typeLabel . ': ' . $data['title']; $eventDesc = $username . ' created [doc:' . $docId . ']' . $data['title'] . '[/doc] (' . $typeLabel . ')'; $stmt2 = $db->prepare(" INSERT INTO events (team_id, title, description, severity, event_type, occurred_at) VALUES (?, ?, ?, 'info', 'document', ?) "); $stmt2->execute([$teamId, $eventTitle, $eventDesc, $data['occurred_at'] ?? date('Y-m-d H:i:s')]); echo json_encode(['id' => $docId]); break; case 'PUT': if ($id) { $data = json_decode(file_get_contents('php://input'), true); $fields = []; $params = []; foreach (['doc_type','team_id','title','content','occurred_at'] as $f) { if (isset($data[$f])) { $fields[] = "$f = ?"; $params[] = $data[$f]; } } if ($fields) { $params[] = $id; $stmt = $db->prepare("UPDATE documents SET " . implode(', ', $fields) . " WHERE id = ?"); $stmt->execute($params); // Create a timeline event for the edit if (isset($data['title']) || isset($data['doc_type'])) { $docType = $data['doc_type'] ?? ''; $docTitle = $data['title'] ?? ''; $teamId = $data['team_id'] ?? null; if ($teamId) { $typeLabels = ['deployment' => 'Deployment', 'attack' => 'Attack', 'incident-report' => 'Incident Report', 'remediation' => 'Remediation', 'exercise' => 'Exercise']; $typeLabel = $typeLabels[$docType] ?? ucfirst($docType); $eventTitle = 'Updated ' . $typeLabel . ': ' . $docTitle; $eventDesc = $username . ' updated [doc:' . $id . ']' . $docTitle . '[/doc] (' . $typeLabel . ')'; $stmt2 = $db->prepare(" INSERT INTO events (team_id, title, description, severity, event_type, occurred_at) VALUES (?, ?, ?, 'info', 'document', ?) "); $stmt2->execute([$teamId, $eventTitle, $eventDesc, date('Y-m-d H:i:s')]); } } } echo json_encode(['updated' => true]); } break; case 'DELETE': if ($id) { $db->prepare("DELETE FROM documents WHERE id = ?")->execute([$id]); echo json_encode(['deleted' => true]); } break; } } function handleAttachments($method, $id, $db) { $username = $_SESSION['neptune_username'] ?? 'Unknown'; $uploadDir = '/var/www/uploads/'; $allowedExtensions = ['pdf', 'md', 'txt', 'xlsx', 'csv', 'pptx', 'evidence']; $allowedMimes = [ 'application/pdf', 'text/markdown', 'text/x-markdown', 'text/plain', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'text/csv', 'application/csv', 'application/vnd.openxmlformats-officedocument.presentationml.presentation', ]; switch ($method) { case 'GET': $eventId = $_GET['event_id'] ?? null; if ($eventId) { $stmt = $db->prepare("SELECT id, event_id, original_name, stored_name, mime_type, file_size, uploaded_by, created_at FROM file_attachments WHERE event_id = ? ORDER BY created_at ASC"); $stmt->execute([$eventId]); echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)); } else { http_response_code(400); echo json_encode(['error' => 'event_id required']); } break; case 'POST': if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { http_response_code(400); echo json_encode(['error' => 'Upload failed']); return; } $eventId = $_POST['event_id'] ?? null; if (!$eventId) { http_response_code(400); echo json_encode(['error' => 'event_id required']); return; } $file = $_FILES['file']; $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($ext, $allowedExtensions)) { http_response_code(400); echo json_encode(['error' => 'File type not allowed. Allowed: ' . implode(', ', $allowedExtensions)]); return; } $mime = $file['type'] ?: ''; $mimeAllowed = empty($mime) || in_array($mime, $allowedMimes); if (!$mimeAllowed && $ext !== 'evidence') { http_response_code(400); echo json_encode(['error' => 'Invalid file content']); return; } $storedName = uniqid('att_', true) . '.' . $ext; $dest = $uploadDir . $storedName; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0755, true); } if (!move_uploaded_file($file['tmp_name'], $dest)) { http_response_code(500); echo json_encode(['error' => 'Failed to save file']); return; } $stmt = $db->prepare(" INSERT INTO file_attachments (event_id, original_name, stored_name, mime_type, file_size, uploaded_by) VALUES (?, ?, ?, ?, ?, ?) "); $stmt->execute([ $eventId, $file['name'], $storedName, $file['type'] ?: 'application/octet-stream', $file['size'], $username ]); echo json_encode(['id' => $db->lastInsertId()]); break; case 'DELETE': if ($id) { $stmt = $db->prepare("SELECT stored_name FROM file_attachments WHERE id = ?"); $stmt->execute([$id]); $att = $stmt->fetch(PDO::FETCH_ASSOC); if ($att) { $path = $uploadDir . $att['stored_name']; if (file_exists($path)) unlink($path); $db->prepare("DELETE FROM file_attachments WHERE id = ?")->execute([$id]); } echo json_encode(['deleted' => true]); } break; } } function handleShapes($method, $id, $db) { switch ($method) { case 'GET': echo json_encode($db->query("SELECT * FROM network_shapes ORDER BY z_index ASC")->fetchAll(PDO::FETCH_ASSOC)); break; case 'POST': $data = json_decode(file_get_contents('php://input'), true); $stmt = $db->prepare(" INSERT INTO network_shapes (label, shape_type, pos_x, pos_y, width, height, color, border_color, opacity, z_index) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['label'] ?? '', $data['shape_type'] ?? 'rectangle', $data['pos_x'] ?? 0, $data['pos_y'] ?? 0, $data['width'] ?? 200, $data['height'] ?? 150, $data['color'] ?? '#1e3a5f', $data['border_color'] ?? '#3b82f6', $data['opacity'] ?? 0.15, $data['z_index'] ?? 0 ]); echo json_encode(['id' => $db->lastInsertId()]); break; case 'PUT': if ($id) { $data = json_decode(file_get_contents('php://input'), true); $fields = []; $params = []; foreach (['label','shape_type','pos_x','pos_y','width','height','color','border_color','opacity','z_index'] as $f) { if (isset($data[$f])) { $fields[] = "$f = ?"; $params[] = $data[$f]; } } if ($fields) { $params[] = $id; $stmt = $db->prepare("UPDATE network_shapes SET " . implode(', ', $fields) . " WHERE id = ?"); $stmt->execute($params); } echo json_encode(['updated' => true]); } break; case 'DELETE': if ($id) { $db->prepare("DELETE FROM network_shapes WHERE id = ?")->execute([$id]); echo json_encode(['deleted' => true]); } break; } }