move controller service logics into a service
This commit is contained in:
@@ -5,107 +5,72 @@ declare(strict_types=1);
|
||||
namespace App\Controller\Admin;
|
||||
|
||||
use App\Entity\IngestProfile;
|
||||
use App\Index\IndexConfigurationProvider;
|
||||
use App\Index\IndexMetaManager;
|
||||
use App\Index\IndexStructureComparator;
|
||||
use App\Repository\IngestProfileRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use App\Service\Admin\IngestProfileAdminService;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
|
||||
#[Route('/admin/ingest-profiles')]
|
||||
class IngestProfileController extends AbstractController
|
||||
final class IngestProfileController extends AbstractController
|
||||
{
|
||||
#[Route('/', name: 'admin_ingest_profile_list')]
|
||||
public function list(
|
||||
IngestProfileRepository $repo,
|
||||
IndexMetaManager $metaManager,
|
||||
IndexConfigurationProvider $provider,
|
||||
IndexStructureComparator $comparator
|
||||
): Response {
|
||||
public function list(IngestProfileAdminService $svc): Response
|
||||
{
|
||||
$data = $svc->listData();
|
||||
|
||||
$profiles = $repo->findBy([], ['version' => 'DESC']);
|
||||
$activeProfile = $repo->findActive();
|
||||
|
||||
$meta = $metaManager->readMeta();
|
||||
$currentStructure = $provider->getConfiguration()->toStructureArray();
|
||||
|
||||
$diff = $comparator->compare($meta, $currentStructure);
|
||||
|
||||
$structureMismatch = false;
|
||||
foreach ($diff as $row) {
|
||||
if (!$row['equal']) {
|
||||
$structureMismatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/ingest_profile/list.html.twig', [
|
||||
'profiles' => $profiles,
|
||||
'activeProfile' => $activeProfile,
|
||||
'indexMeta' => $meta,
|
||||
'diff' => $diff,
|
||||
'structureMismatch' => $structureMismatch,
|
||||
]);
|
||||
return $this->render('admin/ingest_profile/list.html.twig', $data);
|
||||
}
|
||||
|
||||
#[Route('/create', name: 'admin_ingest_profile_create', methods: ['GET', 'POST'])]
|
||||
public function create(
|
||||
Request $request,
|
||||
IngestProfileRepository $repo,
|
||||
EntityManagerInterface $em
|
||||
): Response {
|
||||
|
||||
public function create(Request $request, IngestProfileAdminService $svc): Response
|
||||
{
|
||||
if ($request->isMethod('POST')) {
|
||||
try {
|
||||
$svc->create([
|
||||
'chunk_size' => $request->request->get('chunk_size'),
|
||||
'chunk_overlap' => $request->request->get('chunk_overlap'),
|
||||
'embedding_model' => $request->request->get('embedding_model'),
|
||||
'embedding_dimension' => $request->request->get('embedding_dimension'),
|
||||
'scoring_version' => $request->request->get('scoring_version'),
|
||||
]);
|
||||
|
||||
$latest = $repo->findLatestVersion();
|
||||
$nextVersion = $latest ? $latest->getVersion() + 1 : 1;
|
||||
|
||||
$profile = new IngestProfile(
|
||||
$nextVersion,
|
||||
(int)$request->request->get('chunk_size'),
|
||||
(int)$request->request->get('chunk_overlap'),
|
||||
(string)$request->request->get('embedding_model'),
|
||||
(int)$request->request->get('embedding_dimension'),
|
||||
(int)$request->request->get('scoring_version')
|
||||
);
|
||||
|
||||
$em->persist($profile);
|
||||
$em->flush();
|
||||
|
||||
$this->addFlash('success', 'Ingest-Profil wurde erstellt.');
|
||||
return $this->redirectToRoute('admin_ingest_profile_list');
|
||||
} catch (\Throwable $e) {
|
||||
$this->addFlash('danger', $e->getMessage());
|
||||
// fallthrough -> render form again
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('admin/ingest_profile/create.html.twig');
|
||||
}
|
||||
|
||||
#[Route('/activate/{id}', name: 'admin_ingest_profile_activate')]
|
||||
#[Route('/activate/{id}', name: 'admin_ingest_profile_activate', methods: ['POST'])]
|
||||
public function activate(
|
||||
IngestProfile $profile,
|
||||
IngestProfileRepository $repo,
|
||||
EntityManagerInterface $em
|
||||
IngestProfileAdminService $svc
|
||||
): Response {
|
||||
|
||||
$active = $repo->findActive();
|
||||
if ($active) {
|
||||
$active->deactivate();
|
||||
try {
|
||||
$svc->activate($profile);
|
||||
$this->addFlash('success', 'Ingest-Profil wurde aktiviert.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->addFlash('danger', $e->getMessage());
|
||||
}
|
||||
|
||||
$profile->activate();
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this->redirectToRoute('admin_ingest_profile_list');
|
||||
}
|
||||
|
||||
#[Route('/remove/{id}', name: 'admin_ingest_profile_remove')]
|
||||
public function remove(
|
||||
IngestProfileRepository $repo,
|
||||
string $id
|
||||
): Response {
|
||||
$repo->remove($id);
|
||||
#[Route('/remove/{id}', name: 'admin_ingest_profile_remove', methods: ['POST', 'GET'])]
|
||||
public function remove(string $id, IngestProfileAdminService $svc): Response
|
||||
{
|
||||
try {
|
||||
$svc->remove($id);
|
||||
$this->addFlash('success', 'Ingest-Profil wurde entfernt.');
|
||||
} catch (\Throwable $e) {
|
||||
$this->addFlash('danger', $e->getMessage());
|
||||
}
|
||||
|
||||
return $this->redirectToRoute('admin_ingest_profile_list');
|
||||
}
|
||||
}
|
||||
135
src/Service/Admin/IngestProfileAdminService.php
Normal file
135
src/Service/Admin/IngestProfileAdminService.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Service\Admin;
|
||||
|
||||
use App\Entity\IngestProfile;
|
||||
use App\Index\IndexConfigurationProvider;
|
||||
use App\Index\IndexMetaManager;
|
||||
use App\Index\IndexStructureComparator;
|
||||
use App\Repository\IngestProfileRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final readonly class IngestProfileAdminService
|
||||
{
|
||||
public function __construct(
|
||||
private IngestProfileRepository $repo,
|
||||
private EntityManagerInterface $em,
|
||||
private IndexMetaManager $metaManager,
|
||||
private IndexConfigurationProvider $provider,
|
||||
private IndexStructureComparator $comparator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* profiles: array,
|
||||
* activeProfile: ?IngestProfile,
|
||||
* indexMeta: mixed,
|
||||
* diff: array,
|
||||
* structureMismatch: bool
|
||||
* }
|
||||
*/
|
||||
public function listData(): array
|
||||
{
|
||||
$profiles = $this->repo->findBy([], ['version' => 'DESC']);
|
||||
$activeProfile = $this->repo->findActive();
|
||||
|
||||
$meta = $this->metaManager->readMeta();
|
||||
$currentStructure = $this->provider->getConfiguration()->toStructureArray();
|
||||
|
||||
$diff = $this->comparator->compare($meta, $currentStructure);
|
||||
|
||||
$structureMismatch = false;
|
||||
foreach ($diff as $row) {
|
||||
if (!($row['equal'] ?? false)) {
|
||||
$structureMismatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'profiles' => $profiles,
|
||||
'activeProfile' => $activeProfile,
|
||||
'indexMeta' => $meta,
|
||||
'diff' => $diff,
|
||||
'structureMismatch' => $structureMismatch,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein neues Profil mit nextVersion aus Repo.
|
||||
*
|
||||
* @param array{
|
||||
* chunk_size:mixed,
|
||||
* chunk_overlap:mixed,
|
||||
* embedding_model:mixed,
|
||||
* embedding_dimension:mixed,
|
||||
* scoring_version:mixed
|
||||
* } $data
|
||||
*/
|
||||
public function create(array $data): IngestProfile
|
||||
{
|
||||
$latest = $this->repo->findLatestVersion();
|
||||
$nextVersion = $latest ? $latest->getVersion() + 1 : 1;
|
||||
|
||||
$chunkSize = $this->requireInt($data['chunk_size'] ?? null, 'chunk_size');
|
||||
$chunkOverlap = $this->requireInt($data['chunk_overlap'] ?? null, 'chunk_overlap');
|
||||
$embeddingModel = $this->requireString($data['embedding_model'] ?? null, 'embedding_model');
|
||||
$embeddingDim = $this->requireInt($data['embedding_dimension'] ?? null, 'embedding_dimension');
|
||||
$scoringVersion = $this->requireInt($data['scoring_version'] ?? null, 'scoring_version');
|
||||
|
||||
$profile = new IngestProfile(
|
||||
$nextVersion,
|
||||
$chunkSize,
|
||||
$chunkOverlap,
|
||||
$embeddingModel,
|
||||
$embeddingDim,
|
||||
$scoringVersion
|
||||
);
|
||||
|
||||
$this->em->persist($profile);
|
||||
$this->em->flush();
|
||||
|
||||
return $profile;
|
||||
}
|
||||
|
||||
public function activate(IngestProfile $profile): void
|
||||
{
|
||||
$active = $this->repo->findActive();
|
||||
if ($active instanceof IngestProfile) {
|
||||
$active->deactivate();
|
||||
}
|
||||
|
||||
$profile->activate();
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function remove(string $id): void
|
||||
{
|
||||
// Repo macht intern flush/remove - wie bei dir bereits
|
||||
$this->repo->remove($id);
|
||||
}
|
||||
|
||||
private function requireInt(mixed $value, string $field): int
|
||||
{
|
||||
if ($value === null || $value === '') {
|
||||
throw new \InvalidArgumentException("Missing field: {$field}");
|
||||
}
|
||||
if (is_int($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_numeric($value)) {
|
||||
return (int)$value;
|
||||
}
|
||||
throw new \InvalidArgumentException("Invalid int for {$field}");
|
||||
}
|
||||
|
||||
private function requireString(mixed $value, string $field): string
|
||||
{
|
||||
if (!is_string($value) || trim($value) === '') {
|
||||
throw new \InvalidArgumentException("Invalid string for {$field}");
|
||||
}
|
||||
return trim($value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user