method() !== 'POST') { http_response_code(405); Router::json(['ok' => false, 'error' => 'method_not_allowed']); return; } if (!Session::checkCsrfToken()) { http_response_code(403); Router::json(['ok' => false, 'error' => 'csrf']); return; } $session = app(SessionStoreInterface::class)->all(); $userId = (int) ($session['user']['id'] ?? 0); $type = trim((string) requestInput()->body('type')); $allowedTypes = ['groups', 'bookmarks', 'root']; if (!in_array($type, $allowedTypes, true)) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_type']); return; } $rawItems = requestInput()->body('items'); $items = is_array($rawItems) ? $rawItems : (is_string($rawItems) ? json_decode($rawItems, true) : []); if (!is_array($items)) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_items']); return; } $service = app(BookmarkService::class); if ($type === 'root') { $rootPairs = []; $seenRootIds = []; $seenSortOrders = []; foreach ($items as $item) { if (!is_array($item)) { continue; } $kind = trim((string) ($item['kind'] ?? '')); $id = (int) ($item['id'] ?? 0); $sortOrder = (int) ($item['sort_order'] ?? 0); if (($kind !== 'group' && $kind !== 'bookmark') || $id <= 0 || $sortOrder <= 0) { continue; } $dedupeKey = $kind . ':' . $id; if (isset($seenRootIds[$dedupeKey]) || isset($seenSortOrders[$sortOrder])) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_items']); return; } $seenRootIds[$dedupeKey] = true; $seenSortOrders[$sortOrder] = true; $rootPairs[] = [ 'kind' => $kind, 'id' => $id, 'sort_order' => $sortOrder, ]; } $pairCount = count($rootPairs); if ($pairCount === 0 || $pairCount !== count($items)) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_items']); return; } $ok = $service->reorderRoot($userId, $rootPairs); } else { $pairs = []; $seenIds = []; foreach ($items as $item) { if (!is_array($item)) { continue; } $id = (int) ($item['id'] ?? 0); $sortOrder = (int) ($item['sort_order'] ?? 0); if ($id > 0 && $sortOrder > 0) { if (isset($seenIds[$id])) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_items']); return; } $seenIds[$id] = true; $pairs[] = ['id' => $id, 'sort_order' => $sortOrder]; } } $pairCount = count($pairs); if ($pairCount === 0 || $pairCount !== count($items)) { http_response_code(400); Router::json(['ok' => false, 'error' => 'invalid_items']); return; } $ok = $type === 'groups' ? $service->reorderGroups($userId, $pairs) : $service->reorderBookmarks($userId, $pairs); } if ($ok) { app(SessionStoreInterface::class)->set('user_bookmarks', $service->listGroupedForUser($userId)); Router::json(['ok' => true]); return; } http_response_code(400); Router::json(['ok' => false, 'error' => 'reorder_failed']);