first commit

This commit is contained in:
2026-03-08 04:16:14 -04:00
commit 2bc6071b4e
38 changed files with 8444 additions and 0 deletions

View File

@@ -0,0 +1,313 @@
<?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');
}
}