exec("ALTER TABLE network_nodes ADD COLUMN notes TEXT DEFAULT '' AFTER group_name"); } 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') { $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 'shapes': handleShapes($method, $id, $db); 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 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 { $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 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); } echo json_encode($event); } else { $teamFilter = $_GET['team_id'] ?? 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 = []; if ($teamFilter) { $sql .= " WHERE e.team_id = ?"; $params[] = $teamFilter; } $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); } 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') ]); echo json_encode(['id' => $db->lastInsertId()]); break; case 'DELETE': if ($id) { $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, pos_x, pos_y, notes) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $data['label'], $data['ip_address'] ?? '', $data['node_type'] ?? 'host', $data['status'] ?? 'unknown', $data['group_name'] ?? 'default', $data['pos_x'] ?? 0, $data['pos_y'] ?? 0, $data['notes'] ?? '' ]); 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','pos_x','pos_y','notes'] 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 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; } }