Backup #532
| ID | 532 |
| Dateipfad | /var/www/dev.campus.systemische-tools.de/src/Controller/Api/ExplorerController.php |
| Version | 14 |
| Typ |
modified |
| Größe | 13.4 KB |
| Hash | cea02f42935527840b6240bb3b88f801c2e90af687e360efd158a88933034019 |
| Datum | 2025-12-22 22:18:26 |
| Geändert von | claude-code-hook |
| Grund | Claude Code Pre-Hook Backup vor Write-Operation |
| Datei existiert |
Ja
|
Dateiinhalt
<?php
namespace Controller\Api;
use Framework\Controller;
use Infrastructure\Config\DatabaseFactory;
use Infrastructure\Docs\HybridSearchService;
use Infrastructure\Persistence\SystemExplorerRepository;
/**
* ExplorerController - REST-API für Doc2Vector Pipeline
*
* Endpoints für Dokumente, Seiten, Chunks, Taxonomie und Suche.
* Uses ki_dev database for dokumentation tables.
*/
class ExplorerController extends Controller
{
private \PDO $db;
private SystemExplorerRepository $repository;
public function __construct()
{
$this->db = DatabaseFactory::dev();
$this->repository = new SystemExplorerRepository();
}
/**
* GET /api/v1/explorer/stats
*/
public function stats(): void
{
try {
$chunkStats = $this->repository->getChunkStats();
$this->json([
'success' => true,
'data' => [
'dokumente' => $this->repository->countDokumente(),
'seiten' => $this->repository->countSeiten(),
'chunks' => $chunkStats,
'taxonomy_categories' => $this->repository->getTopTaxonomyCategories(100),
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/dokumente
*/
public function listDokumente(): void
{
try {
$dokumente = $this->repository->getDokumenteWithFullStats();
$this->json([
'success' => true,
'data' => $dokumente,
'meta' => [
'total' => count($dokumente),
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/dokumente/{id}
*/
public function getDokument(string $id): void
{
try {
$stmt = $this->db->prepare(
'SELECT * FROM dokumentation WHERE id = :id AND depth = 0'
);
$stmt->execute(['id' => (int) $id]);
$dokument = $stmt->fetch();
if ($dokument === false) {
$this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404);
return;
}
// Seiten
$stmt = $this->db->prepare(
'SELECT s.id, s.title, s.path, s.depth,
(SELECT COUNT(*) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as chunks_count,
(SELECT COALESCE(SUM(token_count), 0) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as token_count
FROM dokumentation s
WHERE s.parent_id = :id
ORDER BY s.title'
);
$stmt->execute(['id' => (int) $id]);
$seiten = $stmt->fetchAll();
// Taxonomie
$stmt = $this->db->prepare(
'SELECT taxonomy_category, COUNT(*) as count
FROM dokumentation_chunks
WHERE dokumentation_id IN (
SELECT id FROM dokumentation WHERE id = :id OR parent_id = :id2
)
AND taxonomy_category IS NOT NULL
GROUP BY taxonomy_category
ORDER BY count DESC'
);
$stmt->execute(['id' => (int) $id, 'id2' => (int) $id]);
$taxonomy = $stmt->fetchAll();
$this->json([
'success' => true,
'data' => [
'dokument' => $dokument,
'seiten' => $seiten,
'taxonomy' => $taxonomy,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/seiten
*/
public function listSeiten(): void
{
try {
$search = $this->getString('search');
$parentId = $this->getString('parent_id');
$limit = $this->getLimit(100, 50);
$offset = $this->getInt('offset');
$sql = 'SELECT s.id, s.title, s.path, s.depth, s.parent_id,
p.title as parent_title,
(SELECT COUNT(*) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as chunks_count,
(SELECT COALESCE(SUM(token_count), 0) FROM dokumentation_chunks WHERE dokumentation_id = s.id) as token_count
FROM dokumentation s
LEFT JOIN dokumentation p ON s.parent_id = p.id
WHERE s.depth > 0';
$countSql = 'SELECT COUNT(*) FROM dokumentation s WHERE s.depth > 0';
$params = [];
if ($search !== '') {
$sql .= ' AND (s.title LIKE :search OR s.path LIKE :search2)';
$countSql .= ' AND (s.title LIKE :search OR s.path LIKE :search2)';
$params['search'] = '%' . $search . '%';
$params['search2'] = '%' . $search . '%';
}
if ($parentId !== '') {
$sql .= ' AND s.parent_id = :parent';
$countSql .= ' AND s.parent_id = :parent';
$params['parent'] = (int) $parentId;
}
$stmt = $this->db->prepare($countSql);
$stmt->execute($params);
$total = (int) $stmt->fetchColumn();
$sql .= ' ORDER BY p.title, s.depth, s.title LIMIT :limit OFFSET :offset';
$params['limit'] = $limit;
$params['offset'] = $offset;
$stmt = $this->db->prepare($sql);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value, is_int($value) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$stmt->execute();
$seiten = $stmt->fetchAll();
$this->json([
'success' => true,
'data' => $seiten,
'meta' => [
'total' => $total,
'limit' => $limit,
'offset' => $offset,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/seiten/{id}
*/
public function getSeite(string $id): void
{
try {
$stmt = $this->db->prepare(
'SELECT s.*, p.title as parent_title, p.path as parent_path
FROM dokumentation s
LEFT JOIN dokumentation p ON s.parent_id = p.id
WHERE s.id = :id'
);
$stmt->execute(['id' => (int) $id]);
$seite = $stmt->fetch();
if ($seite === false) {
$this->json(['success' => false, 'error' => 'Seite nicht gefunden'], 404);
return;
}
// Chunks
$stmt = $this->db->prepare(
'SELECT c.id, c.chunk_index, c.content, c.token_count, c.taxonomy_category,
c.taxonomy_path, c.entities, c.keywords, c.analysis_status, c.qdrant_id
FROM dokumentation_chunks c
WHERE c.dokumentation_id = :id
ORDER BY c.chunk_index'
);
$stmt->execute(['id' => (int) $id]);
$chunks = $stmt->fetchAll();
// Decode JSON
foreach ($chunks as &$c) {
$c['entities'] = $this->decodeJson($c['entities'] ?? null);
$c['keywords'] = $this->decodeJson($c['keywords'] ?? null);
$c['taxonomy_path'] = $this->decodeJson($c['taxonomy_path'] ?? null);
}
// Unterseiten
$stmt = $this->db->prepare(
'SELECT id, title, path, depth FROM dokumentation WHERE parent_id = :id ORDER BY title'
);
$stmt->execute(['id' => (int) $id]);
$unterseiten = $stmt->fetchAll();
$this->json([
'success' => true,
'data' => [
'seite' => $seite,
'chunks' => $chunks,
'unterseiten' => $unterseiten,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/chunks
*/
public function listChunks(): void
{
try {
$category = $this->getString('category');
$status = $this->getString('status');
$search = $this->getString('search');
$limit = $this->getLimit(100, 50);
$offset = $this->getInt('offset');
$sql = 'SELECT c.id, c.chunk_index, c.content, c.token_count, c.taxonomy_category,
c.analysis_status, c.qdrant_id, c.created_at,
d.id as dokumentation_id, d.title as dokument_title, d.path as dokument_path
FROM dokumentation_chunks c
JOIN dokumentation d ON c.dokumentation_id = d.id
WHERE 1=1';
$countSql = 'SELECT COUNT(*) FROM dokumentation_chunks c WHERE 1=1';
$params = [];
if ($category !== '') {
$sql .= ' AND c.taxonomy_category = :category';
$countSql .= ' AND c.taxonomy_category = :category';
$params['category'] = $category;
}
if ($status !== '') {
$sql .= ' AND c.analysis_status = :status';
$countSql .= ' AND c.analysis_status = :status';
$params['status'] = $status;
}
if ($search !== '') {
$sql .= ' AND (c.content LIKE :search OR c.keywords LIKE :search2)';
$countSql .= ' AND (c.content LIKE :search OR c.keywords LIKE :search2)';
$params['search'] = '%' . $search . '%';
$params['search2'] = '%' . $search . '%';
}
$stmt = $this->db->prepare($countSql);
$stmt->execute($params);
$total = (int) $stmt->fetchColumn();
$sql .= ' ORDER BY c.created_at DESC LIMIT :limit OFFSET :offset';
$params['limit'] = $limit;
$params['offset'] = $offset;
$stmt = $this->db->prepare($sql);
foreach ($params as $key => $value) {
$stmt->bindValue($key, $value, is_int($value) ? \PDO::PARAM_INT : \PDO::PARAM_STR);
}
$stmt->execute();
$chunks = $stmt->fetchAll();
$this->json([
'success' => true,
'data' => $chunks,
'meta' => [
'total' => $total,
'limit' => $limit,
'offset' => $offset,
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/chunks/{id}
*/
public function getChunk(string $id): void
{
try {
$chunk = $this->repository->getChunk((int) $id);
if ($chunk === null) {
$this->json(['success' => false, 'error' => 'Chunk nicht gefunden'], 404);
return;
}
// Repository liefert bereits dekodierte JSON-Felder
$this->json([
'success' => true,
'data' => $chunk,
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/taxonomie
*/
public function taxonomie(): void
{
try {
$this->json([
'success' => true,
'data' => [
'categories' => $this->repository->getTopTaxonomyCategories(100),
'top_keywords' => $this->repository->getTopKeywords(50),
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* GET /api/v1/explorer/entities
*/
public function entities(): void
{
try {
$this->json([
'success' => true,
'data' => $this->repository->getEntitiesGrouped(100),
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
/**
* POST /api/v1/explorer/suche
*/
public function suche(): void
{
try {
$input = $this->getJsonInput();
$query = trim($input['query'] ?? '');
if ($query === '') {
$this->json(['success' => false, 'error' => 'Query ist erforderlich'], 400);
return;
}
$filters = [];
if (!empty($input['category'])) {
$filters['taxonomy_category'] = $input['category'];
}
$limit = min((int) ($input['limit'] ?? 10), 50);
$search = new HybridSearchService();
$results = $search->search($query, $filters, $limit);
$suggestions = $search->suggestRelatedSearches($results);
$this->json([
'success' => true,
'data' => [
'query' => $query,
'results' => $results,
'suggestions' => $suggestions,
'count' => count($results),
],
]);
} catch (\Exception $e) {
$this->jsonError($e->getMessage());
}
}
}
Vollständig herunterladen
Aktionen
Andere Versionen dieser Datei
| ID |
Version |
Typ |
Größe |
Datum |
| 2066 |
45 |
modified |
9.6 KB |
2025-12-28 23:30 |
| 2065 |
44 |
modified |
9.5 KB |
2025-12-28 23:30 |
| 2064 |
43 |
modified |
9.4 KB |
2025-12-28 23:30 |
| 2063 |
42 |
modified |
9.4 KB |
2025-12-28 23:30 |
| 2062 |
41 |
modified |
9.3 KB |
2025-12-28 23:29 |
| 2061 |
40 |
modified |
9.2 KB |
2025-12-28 23:29 |
| 2060 |
39 |
modified |
9.1 KB |
2025-12-28 23:29 |
| 2059 |
38 |
modified |
9.0 KB |
2025-12-28 23:29 |
| 2058 |
37 |
modified |
8.9 KB |
2025-12-28 23:29 |
| 2057 |
36 |
modified |
8.8 KB |
2025-12-28 23:29 |
| 1811 |
35 |
modified |
8.7 KB |
2025-12-27 15:30 |
| 1810 |
34 |
modified |
8.7 KB |
2025-12-27 15:30 |
| 1809 |
33 |
modified |
8.6 KB |
2025-12-27 15:30 |
| 1808 |
32 |
modified |
8.6 KB |
2025-12-27 15:30 |
| 1807 |
31 |
modified |
8.6 KB |
2025-12-27 15:30 |
| 1503 |
30 |
modified |
8.6 KB |
2025-12-25 17:49 |
| 1242 |
29 |
modified |
8.6 KB |
2025-12-25 12:34 |
| 1241 |
28 |
modified |
8.6 KB |
2025-12-25 12:34 |
| 1240 |
27 |
modified |
8.6 KB |
2025-12-25 12:34 |
| 1239 |
26 |
modified |
8.6 KB |
2025-12-25 12:34 |
| 1238 |
25 |
modified |
8.5 KB |
2025-12-25 12:34 |
| 1237 |
24 |
modified |
8.5 KB |
2025-12-25 12:34 |
| 1236 |
23 |
modified |
8.5 KB |
2025-12-25 12:34 |
| 1235 |
22 |
modified |
8.5 KB |
2025-12-25 12:34 |
| 1234 |
21 |
modified |
8.5 KB |
2025-12-25 12:34 |
| 1232 |
20 |
modified |
8.0 KB |
2025-12-25 12:33 |
| 702 |
19 |
modified |
8.0 KB |
2025-12-23 07:53 |
| 625 |
18 |
modified |
8.0 KB |
2025-12-23 04:43 |
| 624 |
17 |
modified |
7.9 KB |
2025-12-23 04:43 |
| 622 |
16 |
modified |
7.9 KB |
2025-12-23 04:43 |
| 591 |
15 |
modified |
7.9 KB |
2025-12-23 04:25 |
| 532 |
14 |
modified |
13.4 KB |
2025-12-22 22:18 |
| 356 |
13 |
modified |
15.0 KB |
2025-12-22 08:19 |
| 354 |
12 |
modified |
16.0 KB |
2025-12-22 08:18 |
| 353 |
11 |
modified |
16.6 KB |
2025-12-22 08:18 |
| 352 |
10 |
modified |
17.3 KB |
2025-12-22 08:18 |
| 351 |
9 |
modified |
18.6 KB |
2025-12-22 08:18 |
| 350 |
8 |
modified |
18.4 KB |
2025-12-22 08:16 |
| 318 |
7 |
modified |
18.5 KB |
2025-12-22 08:06 |
| 317 |
6 |
modified |
18.5 KB |
2025-12-22 08:06 |
| 295 |
5 |
modified |
18.5 KB |
2025-12-22 08:03 |
| 294 |
4 |
modified |
18.5 KB |
2025-12-22 08:03 |
| 285 |
3 |
modified |
18.5 KB |
2025-12-22 07:59 |
| 284 |
2 |
modified |
18.5 KB |
2025-12-22 07:59 |
| 42 |
1 |
modified |
19.2 KB |
2025-12-20 17:24 |
← Zurück zur Übersicht