getSession()->get('authenticated')) { return $this->redirectToRoute('login'); } return null; } // ── Browse ──────────────────────────────────────────────────────────────── #[Route('/{name}', name: 'table_browse', methods: ['GET'])] public function browse(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $lastKey = $request->query->get('lastKey') ? json_decode($request->query->get('lastKey'), true) : null; $limit = (int) $request->query->get('limit', 25); $toastMsg = $request->getSession()->get('toast'); $request->getSession()->remove('toast'); try { $desc = $this->dynamo->describeTable($name); $result = $this->dynamo->scanTable($name, $lastKey, $limit); } catch (\Exception $e) { return $this->render('table/browse.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'error' => $e->getMessage(), 'items' => [], 'columns' => [], 'nextKey' => null, 'prevKeys' => [], 'keySchema' => [], 'count' => 0, 'toast' => $toastMsg, ]); } // Build columns from all items $columns = []; foreach ($result['items'] as $item) { foreach (array_keys($item) as $col) { $columns[$col] = true; } } $columns = array_keys($columns); // Pagination history (stack of prev keys passed as JSON array in query) $prevKeys = $request->query->get('prevKeys') ? json_decode($request->query->get('prevKeys'), true) : []; $keySchema = $this->dynamo->getTableKeySchema($name); return $this->render('table/browse.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'items' => $result['items'], 'columns' => $columns, 'nextKey' => $result['nextKey'] ? json_encode($result['nextKey']) : null, 'prevKeys' => $prevKeys, 'currentKey' => $lastKey ? json_encode($lastKey) : null, 'keySchema' => $keySchema, 'count' => $result['count'], 'scannedCount' => $result['scannedCount'], 'limit' => $limit, 'desc' => $desc, 'error' => null, 'toast' => $toastMsg, ]); } // ── Structure ───────────────────────────────────────────────────────────── #[Route('/{name}/structure', name: 'table_structure', methods: ['GET'])] public function structure(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $error = null; $desc = null; try { $desc = $this->dynamo->describeTable($name); } catch (\Exception $e) { $error = $e->getMessage(); } return $this->render('table/structure.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'desc' => $desc, 'error' => $error, ]); } // ── Query / Scan builder ────────────────────────────────────────────────── #[Route('/{name}/query', name: 'table_query', methods: ['GET', 'POST'])] public function query(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $keySchema = $this->dynamo->getTableKeySchema($name); $results = null; $error = null; $mode = 'query'; // or 'scan' if ($request->isMethod('POST')) { $mode = $request->request->get('mode', 'query'); try { if ($mode === 'scan') { $data = $this->dynamo->scanTable($name, null, 100); $results = $data['items']; } else { $conditions = []; $keys = $request->request->all('cond_key'); $ops = $request->request->all('cond_op'); $vals = $request->request->all('cond_val'); foreach ($keys as $i => $key) { if (!empty($key) && isset($vals[$i])) { $conditions[] = [ 'key' => $key, 'op' => $ops[$i] ?? '=', 'value' => $vals[$i], ]; } } $data = $this->dynamo->queryTable($name, $conditions, null, 100); $results = $data['items']; } } catch (\Exception $e) { $error = $e->getMessage(); } } return $this->render('table/query.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'keySchema' => $keySchema, 'results' => $results, 'error' => $error, 'mode' => $mode, ]); } // ── New Item ────────────────────────────────────────────────────────────── #[Route('/{name}/item/new', name: 'table_item_new', methods: ['GET', 'POST'])] public function newItem(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $keySchema = $this->dynamo->getTableKeySchema($name); $error = null; if ($request->isMethod('POST')) { $json = $request->request->get('item_json', '{}'); try { $item = json_decode($json, true, 512, JSON_THROW_ON_ERROR); $this->dynamo->putItem($name, $item); $request->getSession()->set('toast', ['type' => 'success', 'msg' => 'Item inserted successfully.']); return $this->redirectToRoute('table_browse', ['name' => $name]); } catch (\Exception $e) { $error = $e->getMessage(); } } return $this->render('table/item_form.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'keySchema' => $keySchema, 'item' => null, 'error' => $error, 'mode' => 'new', ]); } // ── Edit Item ───────────────────────────────────────────────────────────── #[Route('/{name}/item/edit', name: 'table_item_edit', methods: ['GET', 'POST'])] public function editItem(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $keySchema = $this->dynamo->getTableKeySchema($name); $error = null; // Key passed as JSON in query string $keyJson = $request->query->get('key', '{}'); $key = json_decode($keyJson, true) ?? []; $item = null; if ($request->isMethod('GET')) { try { $item = $this->dynamo->getItem($name, $key); } catch (\Exception $e) { $error = $e->getMessage(); } } if ($request->isMethod('POST')) { $json = $request->request->get('item_json', '{}'); try { $item = json_decode($json, true, 512, JSON_THROW_ON_ERROR); $this->dynamo->putItem($name, $item); $request->getSession()->set('toast', ['type' => 'success', 'msg' => 'Item updated successfully.']); return $this->redirectToRoute('table_browse', ['name' => $name]); } catch (\Exception $e) { $error = $e->getMessage(); $item = json_decode($json, true); } } return $this->render('table/item_form.html.twig', [ 'tableName' => $name, 'tables' => $tables, 'keySchema' => $keySchema, 'item' => $item, 'error' => $error, 'mode' => 'edit', ]); } // ── Delete Item ─────────────────────────────────────────────────────────── #[Route('/{name}/item/delete', name: 'table_item_delete', methods: ['POST'])] public function deleteItem(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $keyJson = $request->request->get('key', '{}'); $key = json_decode($keyJson, true) ?? []; try { $this->dynamo->deleteItem($name, $key); $request->getSession()->set('toast', ['type' => 'success', 'msg' => 'Item deleted.']); } catch (\Exception $e) { $request->getSession()->set('toast', ['type' => 'error', 'msg' => 'Delete failed: ' . $e->getMessage()]); } return $this->redirectToRoute('table_browse', ['name' => $name]); } // ── Create Table ────────────────────────────────────────────────────────── #[Route('/create', name: 'table_create', methods: ['GET', 'POST'])] public function createTable(Request $request): Response { if ($r = $this->requireAuth($request)) return $r; $tables = $this->dynamo->listTables(); $error = null; if ($request->isMethod('POST')) { $tName = $request->request->get('table_name'); $pkName = $request->request->get('pk_name'); $pkType = $request->request->get('pk_type', 'S'); $skName = $request->request->get('sk_name') ?: null; $skType = $request->request->get('sk_type', 'S') ?: null; try { $this->dynamo->createTable($tName, $pkName, $pkType, $skName, $skType); $request->getSession()->set('toast', ['type' => 'success', 'msg' => "Table \"$tName\" created."]); return $this->redirectToRoute('table_browse', ['name' => $tName]); } catch (\Exception $e) { $error = $e->getMessage(); } } return $this->render('table/create.html.twig', [ 'tables' => $tables, 'error' => $error, ]); } // ── Drop Table ──────────────────────────────────────────────────────────── #[Route('/{name}/drop', name: 'table_drop', methods: ['POST'])] public function dropTable(string $name, Request $request): Response { if ($r = $this->requireAuth($request)) return $r; try { $this->dynamo->deleteTable($name); $request->getSession()->set('toast', ['type' => 'success', 'msg' => "Table \"$name\" dropped."]); } catch (\Exception $e) { $request->getSession()->set('toast', ['type' => 'error', 'msg' => 'Drop failed: ' . $e->getMessage()]); } return $this->redirectToRoute('dashboard'); } }