Backup #532

ID532
Dateipfad/var/www/dev.campus.systemische-tools.de/src/Controller/Api/ExplorerController.php
Version14
Typ modified
Größe13.4 KB
Hashcea02f42935527840b6240bb3b88f801c2e90af687e360efd158a88933034019
Datum2025-12-22 22:18:26
Geändert vonclaude-code-hook
GrundClaude 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

Herunterladen

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