addErrorMiddleware(true, true, true); $container->set('db', function () { return \App\Db::connectFromSession(); }); // --- ROUTES --- // Статическая страница с фронтендом $app->get('/', function (Request $request, Response $response) { // Очень простой view встроен прямо здесь $html = file_get_contents(__DIR__ . '/index.html'); // вынесем в отдельный файл ниже $response->getBody()->write($html); return $response; }); // Логин: сохраняет логин/пароль MariaDB в сессии после проверки $app->post('/api/login', function (Request $request, Response $response) { $data = json_decode((string)$request->getBody(), true); $user = $data['user'] ?? ''; $pass = $data['pass'] ?? ''; try { \App\Db::testConnection($user, $pass); $_SESSION['db_user'] = $user; $_SESSION['db_pass'] = $pass; $payload = ['ok' => true]; $status = 200; } catch (\RuntimeException $e) { $payload = ['ok' => false, 'error' => $e->getMessage()]; $status = 401; } $response->getBody()->write(json_encode($payload)); return $response->withHeader('Content-Type', 'application/json') ->withStatus($status); }); // Middleware на /api/*: проверяем, что аутентифицированы $app->add(function (Request $request, $handler) { $path = $request->getUri()->getPath(); if (str_starts_with($path, '/api/') && $path !== '/api/login') { if (empty($_SESSION['db_user']) || empty($_SESSION['db_pass'])) { $res = new \Slim\Psr7\Response(); $res->getBody()->write(json_encode(['error' => 'Not authenticated'])); return $res->withStatus(401)->withHeader('Content-Type', 'application/json'); } } return $handler->handle($request); }); // API: дерево схем/таблиц $app->get('/api/tree', function (Request $request, Response $response) use ($container) { $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $tree = $meta->getSchemaTree(); $response->getBody()->write(json_encode($tree)); return $response->withHeader('Content-Type', 'application/json'); }); // API: метаданные таблицы $app->get('/api/table/meta', function (Request $request, Response $response) use ($container) { $params = $request->getQueryParams(); $schema = $params['schema'] ?? ''; $table = $params['table'] ?? ''; $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $data = $meta->getTableMeta($schema, $table); $response->getBody()->write(json_encode($data)); return $response->withHeader('Content-Type', 'application/json'); }); // API: данные таблицы $app->post('/api/table/data', function (Request $request, Response $response) use ($container) { $payload = json_decode((string)$request->getBody(), true); $schema = $payload['schema'] ?? ''; $table = $payload['table'] ?? ''; $page = (int)($payload['page'] ?? 1); // Tabulator отправляет 'size', для обратной совместимости также проверяем 'pageSize' $pageSize = (int)($payload['size'] ?? $payload['pageSize'] ?? 50); $filters = $payload['filters'] ?? []; $sort = $payload['sort'] ?? null; $columns = $payload['columns'] ?? []; // ✅ Логирование для отладки error_log("Data request: page=$page, pageSize=$pageSize, filters=" . json_encode($filters) . ", sort=" . json_encode($sort)); $pdo = $container->get('db'); $ds = new \App\DataService($pdo); $resData = $ds->fetchData($schema, $table, $columns, $page, $pageSize, $filters, $sort); // ✅ Логирование ответа error_log("Data response: " . json_encode([ 'data_count' => count($resData['data']), 'last_page' => $resData['last_page'], 'current_page' => $resData['current_page'], 'total' => $resData['total'] ])); $response->getBody()->write(json_encode($resData)); return $response->withHeader('Content-Type', 'application/json'); }); // API: insert / update / delete $app->post('/api/table/insert', function (Request $request, Response $response) use ($container) { try { $payload = json_decode((string)$request->getBody(), true); $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); $schema = $payload['schema']; $table = $payload['table']; $row = $payload['row']; $metaArr = $meta->getTableMeta($schema, $table); $result = $ds->insertRow($schema, $table, $row, $metaArr['columns']); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { $error = ['error' => true, 'message' => $e->getMessage()]; $response->getBody()->write(json_encode($error)); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } }); $app->post('/api/table/update', function (Request $request, Response $response) use ($container) { try { $payload = json_decode((string)$request->getBody(), true); $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); $schema = $payload['schema']; $table = $payload['table']; $row = $payload['row']; $metaArr = $meta->getTableMeta($schema, $table); $result = $ds->updateRow($schema, $table, $row, $metaArr['columns'], $metaArr['primaryKey']); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { $error = ['error' => true, 'message' => $e->getMessage()]; $response->getBody()->write(json_encode($error)); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } }); $app->post('/api/table/delete', function (Request $request, Response $response) use ($container) { try { $payload = json_decode((string)$request->getBody(), true); $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); $schema = $payload['schema']; $table = $payload['table']; $row = $payload['row']; $metaArr = $meta->getTableMeta($schema, $table); $result = $ds->deleteRow($schema, $table, $row, $metaArr['primaryKey']); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { $error = ['error' => true, 'message' => $e->getMessage()]; $response->getBody()->write(json_encode($error)); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } }); // API: импорт CSV (массовая вставка) $app->post('/api/table/import-csv', function (Request $request, Response $response) use ($container) { $body = (string)$request->getBody(); $payload = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { error_log("CSV Import: Invalid JSON - " . json_last_error_msg()); $response->getBody()->write(json_encode(['error' => 'Invalid JSON: ' . json_last_error_msg()])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); $schema = $payload['schema'] ?? ''; $table = $payload['table'] ?? ''; $rows = $payload['rows'] ?? []; if (empty($schema) || empty($table)) { error_log("CSV Import: Missing schema or table"); $response->getBody()->write(json_encode(['error' => 'Schema and table are required'])); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } try { $metaArr = $meta->getTableMeta($schema, $table); $result = $ds->insertMultipleRows($schema, $table, $rows, $metaArr['columns']); // ✅ Очищаем невалидные UTF-8 символы в сообщениях об ошибках if (!empty($result['errorMessages'])) { $result['errorMessages'] = array_map(function($msg) { // Конвертируем в UTF-8 и удаляем невалидные символы return mb_convert_encoding($msg, 'UTF-8', 'UTF-8'); }, $result['errorMessages']); } // ✅ Проверяем, что json_encode работает $json = json_encode($result, JSON_UNESCAPED_UNICODE | JSON_PARTIAL_OUTPUT_ON_ERROR); if ($json === false) { error_log("CSV Import: JSON encoding failed - " . json_last_error_msg()); // Возвращаем упрощённую версию без детальных ошибок $json = json_encode([ 'inserted' => $result['inserted'] ?? 0, 'errors' => $result['errors'] ?? 0, 'errorMessages' => ['Ошибка JSON сериализации. Проверьте логи сервера.'] ], JSON_UNESCAPED_UNICODE); } $response->getBody()->write($json); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { error_log("CSV Import: Critical error - " . $e->getMessage()); error_log("Stack trace: " . $e->getTraceAsString()); $response->getBody()->write(json_encode([ 'error' => mb_convert_encoding($e->getMessage(), 'UTF-8', 'UTF-8'), 'inserted' => 0, 'errors' => 1 ], JSON_UNESCAPED_UNICODE)); return $response->withHeader('Content-Type', 'application/json')->withStatus(500); } }); // API: экспорт CSV $app->post('/api/table/export-csv', function (Request $request, Response $response) use ($container) { $payload = json_decode((string)$request->getBody(), true); $pdo = $container->get('db'); $ds = new \App\DataService($pdo); $schema = $payload['schema'] ?? ''; $table = $payload['table'] ?? ''; $filters = $payload['filters'] ?? []; $sort = $payload['sort'] ?? null; $columns = $payload['columns'] ?? []; $result = $ds->exportCSV($schema, $table, $columns, $filters, $sort); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); }); // API: получить доступные значения для Foreign Key $app->get('/api/fk-values', function (Request $request, Response $response) use ($container) { $params = $request->getQueryParams(); $schema = $params['schema'] ?? ''; $table = $params['table'] ?? ''; $column = $params['column'] ?? ''; $search = $params['search'] ?? ''; // ✅ Добавлен параметр поиска $pdo = $container->get('db'); // ✅ Сначала проверяем количество записей $countSql = "SELECT COUNT(DISTINCT `{$column}`) as cnt FROM `{$schema}`.`{$table}` WHERE `{$column}` IS NOT NULL"; $countStmt = $pdo->prepare($countSql); $countStmt->execute(); $totalCount = (int)$countStmt->fetchColumn(); // ✅ Если есть поиск - фильтруем $whereSql = "`{$column}` IS NOT NULL"; $params = []; if (!empty($search)) { $whereSql .= " AND `{$column}` LIKE :search"; $params[':search'] = '%' . $search . '%'; } // ✅ Определяем лимит (для больших таблиц - без лимита при поиске) $limit = ''; if (empty($search)) { // Без поиска - лимит 1000 значений $limit = 'LIMIT 1000'; } $sql = "SELECT DISTINCT `{$column}` FROM `{$schema}`.`{$table}` WHERE {$whereSql} ORDER BY `{$column}` {$limit}"; $stmt = $pdo->prepare($sql); foreach ($params as $key => $value) { $stmt->bindValue($key, $value); } $stmt->execute(); $values = $stmt->fetchAll(PDO::FETCH_COLUMN); $response->getBody()->write(json_encode([ 'values' => $values, 'total' => $totalCount, 'loaded' => count($values) ])); return $response->withHeader('Content-Type', 'application/json'); }); // API: массовое удаление строк (batch delete) $app->post('/api/table/delete-batch', function (Request $request, Response $response) use ($container) { try { $payload = json_decode((string)$request->getBody(), true); $pdo = $container->get('db'); $meta = new \App\MetaService($pdo); $ds = new \App\DataService($pdo); $schema = $payload['schema']; $table = $payload['table']; $rows = $payload['rows'] ?? []; // Массив строк для удаления $metaArr = $meta->getTableMeta($schema, $table); $result = $ds->deleteMultipleRows($schema, $table, $rows, $metaArr['primaryKey']); $response->getBody()->write(json_encode($result)); return $response->withHeader('Content-Type', 'application/json'); } catch (\Exception $e) { $error = ['error' => true, 'message' => $e->getMessage()]; $response->getBody()->write(json_encode($error)); return $response->withHeader('Content-Type', 'application/json')->withStatus(400); } }); $app->run();