repository = $repository ?? new DokumentationRepository(); $this->syncService = $syncService ?? new ChunkSyncService(); $this->chatUseCase = $chatUseCase ?? new DocumentationChatUseCase(); } /** * GET /api/v1/docs * Liste aller Dokumente mit optionalen Filtern. */ public function index(): void { try { $status = $this->getString('status'); $parentId = $this->getInt('parent_id'); $search = $this->getString('search'); $limit = $this->getLimit(100, 50); $offset = $this->getInt('offset'); $docs = $this->repository->findAll( status: $status ?: null, parentId: $parentId > 0 ? $parentId : null, search: $search ?: null, limit: $limit, offset: $offset ); $total = $this->repository->count( status: $status ?: null, parentId: $parentId > 0 ? $parentId : null, search: $search ?: null ); $this->json([ 'success' => true, 'data' => $docs, 'meta' => [ 'total' => $total, 'limit' => $limit, 'offset' => $offset, ], ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * GET /api/v1/docs/{id} * Einzelnes Dokument mit optionalen Kindern und Breadcrumb. */ public function show(string $id): void { try { $includeChildren = $this->getString('include_children') === '1'; $includeBreadcrumb = $this->getString('include_breadcrumb') === '1'; $doc = $this->repository->findById((int) $id); if ($doc === null) { $this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404); return; } $response = [ 'success' => true, 'data' => $doc, ]; if ($includeChildren) { $response['children'] = $this->repository->findChildren((int) $id); } if ($includeBreadcrumb) { $response['breadcrumb'] = $this->repository->getBreadcrumb((int) $id); } $this->json($response); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * GET /api/v1/docs/path/{path} * Dokument nach Pfad. */ public function showByPath(string $path): void { try { $fullPath = '/' . ltrim($path, '/'); $doc = $this->repository->findByPath($fullPath); if ($doc === null) { $this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404); return; } $this->json([ 'success' => true, 'data' => $doc, ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * POST /api/v1/docs * Neues Dokument erstellen. */ public function store(): void { try { $input = $this->getJsonInput(); $required = ['title', 'slug']; foreach ($required as $field) { if (empty($input[$field])) { $this->json(['success' => false, 'error' => "Feld '$field' ist erforderlich"], 400); return; } } $docId = $this->repository->create([ 'title' => trim($input['title']), 'slug' => trim($input['slug']), 'content' => $input['content'] ?? '', 'description' => $input['description'] ?? null, 'parent_id' => $input['parent_id'] ?? null, 'status' => $input['status'] ?? 'draft', 'sort_order' => $input['sort_order'] ?? 0, ]); $doc = $this->repository->findById($docId); $this->json([ 'success' => true, 'data' => $doc, 'message' => 'Dokument erstellt', ], 201); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * PUT /api/v1/docs/{id} * Dokument aktualisieren. */ public function update(string $id): void { try { $doc = $this->repository->findById((int) $id); if ($doc === null) { $this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404); return; } $input = $this->getJsonInput(); $this->repository->update((int) $id, [ 'title' => $input['title'] ?? $doc['title'], 'content' => $input['content'] ?? $doc['content'], 'description' => $input['description'] ?? $doc['description'], 'status' => $input['status'] ?? $doc['status'], ]); $updated = $this->repository->findById((int) $id); $this->json([ 'success' => true, 'data' => $updated, 'message' => 'Dokument aktualisiert', ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * DELETE /api/v1/docs/{id} * Dokument löschen. */ public function destroy(string $id): void { try { $doc = $this->repository->findById((int) $id); if ($doc === null) { $this->json(['success' => false, 'error' => 'Dokument nicht gefunden'], 404); return; } // Check for children $children = $this->repository->findChildren((int) $id); if (!empty($children)) { $this->json([ 'success' => false, 'error' => 'Dokument hat Unterdokumente. Lösche diese zuerst.', ], 400); return; } $this->repository->delete((int) $id); $this->json([ 'success' => true, 'message' => 'Dokument gelöscht', ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * GET /api/v1/docs/search * Semantic search über Dokumentation. */ public function search(): void { try { $query = $this->getString('q'); $limit = $this->getInt('limit', 5); $category = $this->getString('category'); if ($query === '') { $this->json(['success' => false, 'error' => 'Keine Suchanfrage'], 400); return; } if ($category !== '') { $results = $this->syncService->searchByTaxonomy($query, $category, $limit); } else { $results = $this->syncService->search($query, $limit); } $this->json([ 'success' => true, 'data' => $results, 'meta' => [ 'query' => $query, 'limit' => $limit, 'count' => count($results), ], ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * GET /api/v1/docs/hierarchy * Vollständiger Dokumentationsbaum. */ public function hierarchy(): void { try { $tree = $this->repository->getTree(); $this->json([ 'success' => true, 'data' => $tree, ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } /** * POST /api/v1/docs/chat * Chat mit Dokumentation (RAG). */ public function chat(): void { try { $input = $this->getJsonInput(); $question = trim($input['question'] ?? ''); $model = $input['model'] ?? 'mistral'; $limit = (int) ($input['limit'] ?? 5); if ($question === '') { $this->json(['success' => false, 'error' => 'Keine Frage angegeben'], 400); return; } $result = $this->chatUseCase->execute($question, $model, $limit); $this->json([ 'success' => true, 'data' => $result, ]); } catch (\Exception $e) { $this->jsonError($e->getMessage()); } } }