fedi-feed-router/app/Actions/PublishRouteArticleAction.php

139 lines
4.6 KiB
PHP

<?php
namespace App\Actions;
use App\Enums\LogLevelEnum;
use App\Enums\NotificationSeverityEnum;
use App\Enums\NotificationTypeEnum;
use App\Enums\PublishStatusEnum;
use App\Events\ActionPerformed;
use App\Exceptions\PublishException;
use App\Models\Article;
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 {
$extractedData = $this->resolvePublishData($article);
$outcome = $this->hasPublishableContent($extractedData)
? $this->publishingService->publishRouteArticle($routeArticle, $extractedData)
: PublishOutcome::failure('Could not recover the article content to publish');
} catch (Exception $e) {
$routeArticle->update(['publish_status' => PublishStatusEnum::ERROR]);
$routeArticle->recordPublishAttemptFailed();
ActionPerformed::dispatch('Failed to publish article', LogLevelEnum::ERROR, [
'article_id' => $article->id,
'error' => $e->getMessage(),
]);
$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;
}
/**
* @param array<string, mixed> $extractedData
*/
private function hasPublishableContent(array $extractedData): bool
{
return ! empty($extractedData['description']);
}
/**
* @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,
];
}
private function recordPublished(RouteArticle $routeArticle): void
{
$routeArticle->update(['publish_status' => PublishStatusEnum::PUBLISHED]);
$routeArticle->clearPublishAttempts();
ActionPerformed::dispatch('Published article', LogLevelEnum::INFO, [
'article_id' => $routeArticle->article->id,
'title' => $routeArticle->article->title,
]);
}
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,
]);
}
private function recordFailed(RouteArticle $routeArticle, PublishOutcome $outcome): void
{
$article = $routeArticle->article;
$routeArticle->update(['publish_status' => PublishStatusEnum::ERROR]);
$routeArticle->recordPublishAttemptFailed();
ActionPerformed::dispatch('No publication created for article', LogLevelEnum::WARNING, [
'article_id' => $article->id,
'title' => $article->title,
'reason' => $outcome->reason,
'attempt' => $routeArticle->publish_attempts,
]);
$this->notificationService->send(
NotificationTypeEnum::PUBLISH_FAILED,
NotificationSeverityEnum::WARNING,
"Publish failed: {$article->title}",
$outcome->reason ?? 'No publication was created for this article.',
$article,
);
}
}