feat(setting): étendre ContentSource avec champs de test et domain model
- Ajouter testSlug, testChapterNumber, baseUrl sur ContentSource (entité, domain model, migration) - Exposer ces champs dans les Resources, Processors, Providers et Mapper - Mettre à jour store Pinia, repository API et composants Vue (form, card, liste)
This commit is contained in:
parent
b0ce36096f
commit
795cbeccc3
@@ -12,6 +12,8 @@ readonly class UpsertContentSourceCommand
|
||||
public ?string $imageSelector = null,
|
||||
public ?string $nextPageSelector = null,
|
||||
public ?string $chapterSelector = null,
|
||||
public ?string $testSlug = null,
|
||||
public ?float $testChapterNumber = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ readonly class UpsertContentSourceCommandHandler
|
||||
imageSelector: $command->imageSelector,
|
||||
nextPageSelector: $command->nextPageSelector,
|
||||
chapterSelector: $command->chapterSelector,
|
||||
testSlug: $command->testSlug,
|
||||
testChapterNumber: $command->testChapterNumber,
|
||||
);
|
||||
$this->contentSourceRepository->save($contentSource);
|
||||
}
|
||||
@@ -38,6 +40,8 @@ readonly class UpsertContentSourceCommandHandler
|
||||
imageSelector: $command->imageSelector,
|
||||
nextPageSelector: $command->nextPageSelector,
|
||||
chapterSelector: $command->chapterSelector,
|
||||
testSlug: $command->testSlug,
|
||||
testChapterNumber: $command->testChapterNumber,
|
||||
);
|
||||
$this->contentSourceRepository->save($contentSource);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,11 @@ readonly class ContentSourceResponse
|
||||
public ?string $nextPageSelector,
|
||||
public ?string $chapterSelector,
|
||||
public string $cleanBaseUrl,
|
||||
public ?string $testSlug = null,
|
||||
public ?float $testChapterNumber = null,
|
||||
public string $healthStatus = 'unknown',
|
||||
public ?\DateTimeImmutable $healthLastTestedAt = null,
|
||||
public ?string $healthLastError = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -29,6 +34,11 @@ readonly class ContentSourceResponse
|
||||
nextPageSelector: $contentSource->getNextPageSelector(),
|
||||
chapterSelector: $contentSource->getChapterSelector(),
|
||||
cleanBaseUrl: $contentSource->getCleanBaseUrl(),
|
||||
testSlug: $contentSource->getTestSlug(),
|
||||
testChapterNumber: $contentSource->getTestChapterNumber(),
|
||||
healthStatus: $contentSource->getHealthStatus(),
|
||||
healthLastTestedAt: $contentSource->getHealthLastTestedAt(),
|
||||
healthLastError: $contentSource->getHealthLastError(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,11 @@ final class ContentSource
|
||||
private ?string $imageSelector = null,
|
||||
private ?string $nextPageSelector = null,
|
||||
private ?string $chapterSelector = null,
|
||||
private ?string $testSlug = null,
|
||||
private ?float $testChapterNumber = null,
|
||||
private string $healthStatus = 'unknown',
|
||||
private ?\DateTimeImmutable $healthLastTestedAt = null,
|
||||
private ?string $healthLastError = null,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -50,6 +55,44 @@ final class ContentSource
|
||||
return $this->chapterSelector;
|
||||
}
|
||||
|
||||
public function getTestSlug(): ?string
|
||||
{
|
||||
return $this->testSlug;
|
||||
}
|
||||
|
||||
public function getTestChapterNumber(): ?float
|
||||
{
|
||||
return $this->testChapterNumber;
|
||||
}
|
||||
|
||||
public function getHealthStatus(): string
|
||||
{
|
||||
return $this->healthStatus;
|
||||
}
|
||||
|
||||
public function getHealthLastTestedAt(): ?\DateTimeImmutable
|
||||
{
|
||||
return $this->healthLastTestedAt;
|
||||
}
|
||||
|
||||
public function getHealthLastError(): ?string
|
||||
{
|
||||
return $this->healthLastError;
|
||||
}
|
||||
|
||||
public function updateTestConfig(?string $testSlug, ?float $testChapterNumber): void
|
||||
{
|
||||
$this->testSlug = $testSlug;
|
||||
$this->testChapterNumber = $testChapterNumber;
|
||||
}
|
||||
|
||||
public function updateHealthStatus(string $status, ?\DateTimeImmutable $testedAt = null, ?string $error = null): void
|
||||
{
|
||||
$this->healthStatus = $status;
|
||||
$this->healthLastTestedAt = $testedAt;
|
||||
$this->healthLastError = $error;
|
||||
}
|
||||
|
||||
public function updateId(int $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
@@ -71,6 +114,8 @@ final class ContentSource
|
||||
?string $imageSelector = null,
|
||||
?string $nextPageSelector = null,
|
||||
?string $chapterSelector = null,
|
||||
?string $testSlug = null,
|
||||
?float $testChapterNumber = null,
|
||||
): self {
|
||||
return new self(
|
||||
id: null,
|
||||
@@ -80,6 +125,8 @@ final class ContentSource
|
||||
imageSelector: $imageSelector,
|
||||
nextPageSelector: $nextPageSelector,
|
||||
chapterSelector: $chapterSelector,
|
||||
testSlug: $testSlug,
|
||||
testChapterNumber: $testChapterNumber,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,6 +137,8 @@ final class ContentSource
|
||||
?string $imageSelector = null,
|
||||
?string $nextPageSelector = null,
|
||||
?string $chapterSelector = null,
|
||||
?string $testSlug = null,
|
||||
?float $testChapterNumber = null,
|
||||
): void {
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->chapterUrlFormat = $chapterUrlFormat;
|
||||
@@ -97,5 +146,7 @@ final class ContentSource
|
||||
$this->imageSelector = $imageSelector;
|
||||
$this->nextPageSelector = $nextPageSelector;
|
||||
$this->chapterSelector = $chapterSelector;
|
||||
$this->testSlug = $testSlug;
|
||||
$this->testChapterNumber = $testChapterNumber;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,11 @@ class GetContentSourceResource
|
||||
public readonly ?string $nextPageSelector,
|
||||
public readonly ?string $chapterSelector,
|
||||
public readonly string $cleanBaseUrl,
|
||||
public readonly ?string $testSlug = null,
|
||||
public readonly ?float $testChapterNumber = null,
|
||||
public readonly string $healthStatus = 'unknown',
|
||||
public readonly ?\DateTimeImmutable $healthLastTestedAt = null,
|
||||
public readonly ?string $healthLastError = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ class UpsertContentSourceResource
|
||||
public readonly ?string $imageSelector = null,
|
||||
public readonly ?string $nextPageSelector = null,
|
||||
public readonly ?string $chapterSelector = null,
|
||||
public readonly ?string $testSlug = null,
|
||||
public readonly ?float $testChapterNumber = null,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ readonly class UpsertContentSourceStateProcessor implements ProcessorInterface
|
||||
imageSelector: $data->imageSelector,
|
||||
nextPageSelector: $data->nextPageSelector,
|
||||
chapterSelector: $data->chapterSelector,
|
||||
testSlug: $data->testSlug,
|
||||
testChapterNumber: $data->testChapterNumber,
|
||||
);
|
||||
|
||||
$this->handler->handle($command);
|
||||
|
||||
@@ -32,6 +32,11 @@ readonly class GetContentSourceStateProvider implements ProviderInterface
|
||||
nextPageSelector: $response->nextPageSelector,
|
||||
chapterSelector: $response->chapterSelector,
|
||||
cleanBaseUrl: $response->cleanBaseUrl,
|
||||
testSlug: $response->testSlug,
|
||||
testChapterNumber: $response->testChapterNumber,
|
||||
healthStatus: $response->healthStatus,
|
||||
healthLastTestedAt: $response->healthLastTestedAt,
|
||||
healthLastError: $response->healthLastError,
|
||||
);
|
||||
} catch (ContentSourceNotFoundException $e) {
|
||||
throw new NotFoundHttpException($e->getMessage());
|
||||
|
||||
@@ -30,6 +30,11 @@ readonly class ListContentSourceStateProvider implements ProviderInterface
|
||||
nextPageSelector: $contentSourceResponse->nextPageSelector,
|
||||
chapterSelector: $contentSourceResponse->chapterSelector,
|
||||
cleanBaseUrl: $contentSourceResponse->cleanBaseUrl,
|
||||
testSlug: $contentSourceResponse->testSlug,
|
||||
testChapterNumber: $contentSourceResponse->testChapterNumber,
|
||||
healthStatus: $contentSourceResponse->healthStatus,
|
||||
healthLastTestedAt: $contentSourceResponse->healthLastTestedAt,
|
||||
healthLastError: $contentSourceResponse->healthLastError,
|
||||
),
|
||||
$response->contentSources
|
||||
);
|
||||
|
||||
@@ -17,6 +17,11 @@ readonly class ContentSourceMapper
|
||||
imageSelector: $entity->getImageSelector(),
|
||||
nextPageSelector: $entity->getNextPageSelector(),
|
||||
chapterSelector: $entity->getChapterSelector(),
|
||||
testSlug: $entity->getTestSlug(),
|
||||
testChapterNumber: $entity->getTestChapterNumber(),
|
||||
healthStatus: $entity->getHealthStatus(),
|
||||
healthLastTestedAt: $entity->getHealthLastTestedAt(),
|
||||
healthLastError: $entity->getHealthLastError(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -29,7 +34,12 @@ readonly class ContentSourceMapper
|
||||
->setScrapingType($contentSource->getScrapingType())
|
||||
->setImageSelector($contentSource->getImageSelector())
|
||||
->setNextPageSelector($contentSource->getNextPageSelector())
|
||||
->setChapterSelector($contentSource->getChapterSelector());
|
||||
->setChapterSelector($contentSource->getChapterSelector())
|
||||
->setTestSlug($contentSource->getTestSlug())
|
||||
->setTestChapterNumber($contentSource->getTestChapterNumber())
|
||||
->setHealthStatus($contentSource->getHealthStatus())
|
||||
->setHealthLastTestedAt($contentSource->getHealthLastTestedAt())
|
||||
->setHealthLastError($contentSource->getHealthLastError());
|
||||
|
||||
return $entity;
|
||||
}
|
||||
@@ -41,7 +51,12 @@ readonly class ContentSourceMapper
|
||||
->setScrapingType($contentSource->getScrapingType())
|
||||
->setImageSelector($contentSource->getImageSelector())
|
||||
->setNextPageSelector($contentSource->getNextPageSelector())
|
||||
->setChapterSelector($contentSource->getChapterSelector());
|
||||
->setChapterSelector($contentSource->getChapterSelector())
|
||||
->setTestSlug($contentSource->getTestSlug())
|
||||
->setTestChapterNumber($contentSource->getTestChapterNumber())
|
||||
->setHealthStatus($contentSource->getHealthStatus())
|
||||
->setHealthLastTestedAt($contentSource->getHealthLastTestedAt())
|
||||
->setHealthLastError($contentSource->getHealthLastError());
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domain\Setting\Infrastructure\Persistence\Repository;
|
||||
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\ContentSourceForHealthCheckInterface;
|
||||
use App\Domain\Scraping\Domain\Contract\Repository\ContentSourceHealthRepositoryInterface;
|
||||
use App\Domain\Scraping\Domain\Model\ValueObject\ContentSourceHealthCheckData;
|
||||
use App\Entity\ContentSource as ContentSourceEntity;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\Mercure\HubInterface;
|
||||
use Symfony\Component\Mercure\Update;
|
||||
|
||||
readonly class DoctrineContentSourceForHealthCheckRepository implements ContentSourceForHealthCheckInterface, ContentSourceHealthRepositoryInterface
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $entityManager,
|
||||
private HubInterface $hub,
|
||||
private LoggerInterface $logger,
|
||||
) {
|
||||
}
|
||||
|
||||
public function getAll(): array
|
||||
{
|
||||
$entities = $this->entityManager->getRepository(ContentSourceEntity::class)->findAll();
|
||||
|
||||
return array_map(
|
||||
fn (ContentSourceEntity $entity) => new ContentSourceHealthCheckData(
|
||||
id: $entity->getId(),
|
||||
baseUrl: $entity->getBaseUrl(),
|
||||
chapterUrlFormat: $entity->getChapterUrlFormat(),
|
||||
scrapingType: $entity->getScrapingType(),
|
||||
imageSelector: $entity->getImageSelector(),
|
||||
nextPageSelector: $entity->getNextPageSelector(),
|
||||
chapterSelector: $entity->getChapterSelector(),
|
||||
testSlug: $entity->getTestSlug(),
|
||||
testChapterNumber: $entity->getTestChapterNumber(),
|
||||
),
|
||||
$entities
|
||||
);
|
||||
}
|
||||
|
||||
public function markAsTesting(int $sourceId): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ContentSourceEntity::class, $sourceId);
|
||||
if (!$entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->setHealthStatus('testing')
|
||||
->setHealthLastError(null);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->publishUpdate($sourceId, 'testing', null);
|
||||
}
|
||||
|
||||
public function markAsHealthy(int $sourceId, \DateTimeImmutable $testedAt): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ContentSourceEntity::class, $sourceId);
|
||||
if (!$entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->setHealthStatus('ok')
|
||||
->setHealthLastTestedAt($testedAt)
|
||||
->setHealthLastError(null);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->publishUpdate($sourceId, 'ok', null);
|
||||
}
|
||||
|
||||
public function markAsUnhealthy(int $sourceId, \DateTimeImmutable $testedAt, string $error): void
|
||||
{
|
||||
$entity = $this->entityManager->find(ContentSourceEntity::class, $sourceId);
|
||||
if (!$entity) {
|
||||
return;
|
||||
}
|
||||
|
||||
$entity->setHealthStatus('ko')
|
||||
->setHealthLastTestedAt($testedAt)
|
||||
->setHealthLastError($error);
|
||||
|
||||
$this->entityManager->flush();
|
||||
$this->publishUpdate($sourceId, 'ko', $error);
|
||||
}
|
||||
|
||||
private function publishUpdate(int $sourceId, string $status, ?string $error): void
|
||||
{
|
||||
try {
|
||||
$this->hub->publish(new Update(
|
||||
"scrapers/health/{$sourceId}",
|
||||
json_encode(['sourceId' => $sourceId, 'status' => $status, 'error' => $error])
|
||||
));
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->warning('Mercure publish failed for scraper health update', [
|
||||
'sourceId' => $sourceId,
|
||||
'status' => $status,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user