314 lines
12 KiB
PHP
314 lines
12 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
namespace App\Controller;
|
||
|
|
|
||
|
|
use App\Service\DynamoService;
|
||
|
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||
|
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||
|
|
use Symfony\Component\HttpFoundation\Request;
|
||
|
|
use Symfony\Component\HttpFoundation\Response;
|
||
|
|
use Symfony\Component\Routing\Annotation\Route;
|
||
|
|
|
||
|
|
#[Route('/table')]
|
||
|
|
class TableController extends AbstractController
|
||
|
|
{
|
||
|
|
public function __construct(private DynamoService $dynamo) {}
|
||
|
|
|
||
|
|
private function requireAuth(Request $request): ?Response
|
||
|
|
{
|
||
|
|
if (!$request->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');
|
||
|
|
}
|
||
|
|
}
|