2026-08-02 17:16:30 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Actions;
|
|
|
|
|
|
2026-08-10 20:44:57 +02:00
|
|
|
use App\Enums\ActivityTypeEnum;
|
2026-08-02 17:16:30 +02:00
|
|
|
use App\Enums\LogLevelEnum;
|
|
|
|
|
use App\Enums\NotificationSeverityEnum;
|
|
|
|
|
use App\Enums\NotificationTypeEnum;
|
|
|
|
|
use App\Enums\PublishStatusEnum;
|
|
|
|
|
use App\Events\ActionPerformed;
|
2026-08-10 20:44:57 +02:00
|
|
|
use App\Events\ActivityLogged;
|
2026-08-02 17:16:30 +02:00
|
|
|
use App\Exceptions\PublishException;
|
2026-08-09 20:41:07 +02:00
|
|
|
use App\Models\Article;
|
2026-08-02 17:16:30 +02:00
|
|
|
use App\Models\RouteArticle;
|
|
|
|
|
use App\Services\Article\ArticleFetcher;
|
|
|
|
|
use App\Services\Notification\NotificationService;
|
|
|
|
|
use App\Services\Publishing\ArticlePublishingService;
|
|
|
|
|
use App\Services\Publishing\PublishOutcome;
|
|
|
|
|
use Exception;
|
|
|
|
|
|
|
|
|
|
class PublishRouteArticleAction
|
|
|
|
|
{
|
|
|
|
|
public function __construct(
|
|
|
|
|
private ArticleFetcher $articleFetcher,
|
|
|
|
|
private ArticlePublishingService $publishingService,
|
|
|
|
|
private NotificationService $notificationService,
|
|
|
|
|
) {}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @throws PublishException
|
|
|
|
|
*/
|
|
|
|
|
public function execute(RouteArticle $routeArticle): PublishOutcome
|
|
|
|
|
{
|
|
|
|
|
$article = $routeArticle->article;
|
|
|
|
|
|
|
|
|
|
$routeArticle->update(['publish_status' => PublishStatusEnum::PUBLISHING]);
|
|
|
|
|
|
|
|
|
|
try {
|
2026-08-09 20:41:07 +02:00
|
|
|
$extractedData = $this->resolvePublishData($article);
|
2026-08-09 23:28:59 +02:00
|
|
|
|
|
|
|
|
$outcome = $this->hasPublishableContent($extractedData)
|
|
|
|
|
? $this->publishingService->publishRouteArticle($routeArticle, $extractedData)
|
|
|
|
|
: PublishOutcome::failure('Could not recover the article content to publish');
|
2026-08-02 17:16:30 +02:00
|
|
|
} catch (Exception $e) {
|
2026-08-13 21:36:57 +02:00
|
|
|
$routeArticle->recordPublishFailed($e->getMessage());
|
2026-08-02 17:16:30 +02:00
|
|
|
|
|
|
|
|
ActionPerformed::dispatch('Failed to publish article', LogLevelEnum::ERROR, [
|
|
|
|
|
'article_id' => $article->id,
|
|
|
|
|
'error' => $e->getMessage(),
|
|
|
|
|
]);
|
|
|
|
|
|
2026-08-10 20:44:57 +02:00
|
|
|
ActivityLogged::dispatch(
|
|
|
|
|
ActivityTypeEnum::ERROR,
|
|
|
|
|
"Failed to publish \"{$article->title}\"",
|
|
|
|
|
['error' => $e->getMessage()],
|
|
|
|
|
$article,
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-02 17:16:30 +02:00
|
|
|
$this->notificationService->send(
|
|
|
|
|
NotificationTypeEnum::PUBLISH_FAILED,
|
|
|
|
|
NotificationSeverityEnum::ERROR,
|
|
|
|
|
"Publish failed: {$article->title}",
|
|
|
|
|
$e->getMessage(),
|
|
|
|
|
$article,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
throw $e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match (true) {
|
|
|
|
|
$outcome->succeeded() => $this->recordPublished($routeArticle),
|
|
|
|
|
$outcome->wasSkipped() => $this->recordSkipped($routeArticle, $outcome),
|
|
|
|
|
default => $this->recordFailed($routeArticle, $outcome),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return $outcome;
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 23:28:59 +02:00
|
|
|
/**
|
|
|
|
|
* @param array<string, mixed> $extractedData
|
|
|
|
|
*/
|
|
|
|
|
private function hasPublishableContent(array $extractedData): bool
|
|
|
|
|
{
|
|
|
|
|
return ! empty($extractedData['description']);
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-09 20:41:07 +02:00
|
|
|
/**
|
|
|
|
|
* @return array<string, mixed>
|
|
|
|
|
*/
|
|
|
|
|
private function resolvePublishData(Article $article): array
|
|
|
|
|
{
|
|
|
|
|
if (empty($article->description) && empty($article->image_url)) {
|
|
|
|
|
return $this->articleFetcher->fetchArticleData($article);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [
|
|
|
|
|
'title' => $article->title,
|
|
|
|
|
'description' => $article->description,
|
|
|
|
|
'thumbnail' => $article->image_url,
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
2026-08-02 17:16:30 +02:00
|
|
|
private function recordPublished(RouteArticle $routeArticle): void
|
|
|
|
|
{
|
2026-08-13 21:36:57 +02:00
|
|
|
$routeArticle->update([
|
|
|
|
|
'publish_status' => PublishStatusEnum::PUBLISHED,
|
|
|
|
|
'publish_error' => null,
|
|
|
|
|
]);
|
2026-08-02 17:16:30 +02:00
|
|
|
|
|
|
|
|
ActionPerformed::dispatch('Published article', LogLevelEnum::INFO, [
|
|
|
|
|
'article_id' => $routeArticle->article->id,
|
|
|
|
|
'title' => $routeArticle->article->title,
|
|
|
|
|
]);
|
2026-08-10 20:44:57 +02:00
|
|
|
|
|
|
|
|
ActivityLogged::dispatch(
|
|
|
|
|
ActivityTypeEnum::PUBLISH,
|
|
|
|
|
"Published \"{$routeArticle->article->title}\"",
|
|
|
|
|
['route_article_id' => $routeArticle->id],
|
|
|
|
|
$routeArticle->article,
|
|
|
|
|
);
|
2026-08-02 17:16:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function recordSkipped(RouteArticle $routeArticle, PublishOutcome $outcome): void
|
|
|
|
|
{
|
|
|
|
|
$routeArticle->update(['publish_status' => PublishStatusEnum::SKIPPED]);
|
|
|
|
|
|
|
|
|
|
ActionPerformed::dispatch('Skipped publishing article', LogLevelEnum::INFO, [
|
|
|
|
|
'article_id' => $routeArticle->article->id,
|
|
|
|
|
'title' => $routeArticle->article->title,
|
|
|
|
|
'reason' => $outcome->reason,
|
|
|
|
|
]);
|
2026-08-10 20:44:57 +02:00
|
|
|
|
|
|
|
|
ActivityLogged::dispatch(
|
|
|
|
|
ActivityTypeEnum::PUBLISH,
|
|
|
|
|
"Skipped \"{$routeArticle->article->title}\"",
|
|
|
|
|
['skipped' => true, 'reason' => $outcome->reason],
|
|
|
|
|
$routeArticle->article,
|
|
|
|
|
);
|
2026-08-02 17:16:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private function recordFailed(RouteArticle $routeArticle, PublishOutcome $outcome): void
|
|
|
|
|
{
|
|
|
|
|
$article = $routeArticle->article;
|
|
|
|
|
|
2026-08-13 21:36:57 +02:00
|
|
|
$routeArticle->recordPublishFailed($outcome->reason ?? 'Publishing failed');
|
2026-08-02 17:16:30 +02:00
|
|
|
|
|
|
|
|
ActionPerformed::dispatch('No publication created for article', LogLevelEnum::WARNING, [
|
|
|
|
|
'article_id' => $article->id,
|
|
|
|
|
'title' => $article->title,
|
|
|
|
|
'reason' => $outcome->reason,
|
|
|
|
|
]);
|
|
|
|
|
|
2026-08-10 20:44:57 +02:00
|
|
|
ActivityLogged::dispatch(
|
|
|
|
|
ActivityTypeEnum::ERROR,
|
|
|
|
|
"Failed to publish \"{$article->title}\"",
|
2026-08-13 21:36:57 +02:00
|
|
|
['reason' => $outcome->reason],
|
2026-08-10 20:44:57 +02:00
|
|
|
$article,
|
|
|
|
|
);
|
|
|
|
|
|
2026-08-02 17:16:30 +02:00
|
|
|
$this->notificationService->send(
|
|
|
|
|
NotificationTypeEnum::PUBLISH_FAILED,
|
|
|
|
|
NotificationSeverityEnum::WARNING,
|
|
|
|
|
"Publish failed: {$article->title}",
|
|
|
|
|
$outcome->reason ?? 'No publication was created for this article.',
|
|
|
|
|
$article,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|