fedi-feed-router/app/Services/Publishing/ArticlePublishingService.php

113 lines
3.7 KiB
PHP

<?php
namespace App\Services\Publishing;
use App\Enums\PlatformEnum;
use App\Exceptions\PublishException;
use App\Models\Article;
use App\Models\ArticlePublication;
use App\Models\PlatformChannel;
use App\Models\PlatformChannelPost;
use App\Models\RouteArticle;
use App\Modules\Lemmy\Services\LemmyPublisher;
use App\Services\Log\LogSaver;
use Exception;
use RuntimeException;
class ArticlePublishingService
{
public function __construct(private LogSaver $logSaver) {}
/**
* Factory seam to create publisher instances (helps testing without network calls)
*/
protected function makePublisher(mixed $account): LemmyPublisher
{
return new LemmyPublisher($account);
}
/**
* Publish an article to the channel specified by a route_article record.
*
* @param array<string, mixed> $extractedData
*
* @throws PublishException
*/
public function publishRouteArticle(RouteArticle $routeArticle, array $extractedData): ?ArticlePublication
{
$article = $routeArticle->article;
$channel = $routeArticle->platformChannel;
if (! $channel) {
throw new PublishException($article, PlatformEnum::LEMMY, new RuntimeException('ROUTE_ARTICLE_MISSING_CHANNEL'));
}
if (! $channel->relationLoaded('platformInstance')) {
$channel->load(['platformInstance', 'activePlatformAccounts']);
}
$account = $channel->activePlatformAccounts()->first();
if (! $account) {
$this->logSaver->warning('No active account for channel', $channel, [
'article_id' => $article->id,
'route_article_id' => $routeArticle->id,
]);
return null;
}
return $this->publishToChannel($article, $extractedData, $channel, $account);
}
/**
* @param array<string, mixed> $extractedData
*/
private function publishToChannel(Article $article, array $extractedData, PlatformChannel $channel, mixed $account): ?ArticlePublication
{
try {
// Check if this URL or title was already posted to this channel
$title = $extractedData['title'] ?? $article->title;
if (PlatformChannelPost::duplicateExists(
$channel->platformInstance->platform,
(string) $channel->channel_id,
$article->url,
$title
)) {
$this->logSaver->info('Skipping duplicate: URL or title already posted to channel', $channel, [
'article_id' => $article->id,
'url' => $article->url,
'title' => $title,
]);
return null;
}
$publisher = $this->makePublisher($account);
$postData = $publisher->publishToChannel($article, $extractedData, $channel);
$publication = ArticlePublication::create([
'article_id' => $article->id,
'post_id' => $postData['post_view']['post']['id'],
'platform_channel_id' => $channel->id,
'published_by' => $account->username,
'published_at' => now(),
'platform' => $channel->platformInstance->platform->value,
'publication_data' => $postData,
]);
$this->logSaver->info('Published to channel', $channel, [
'article_id' => $article->id,
]);
return $publication;
} catch (Exception $e) {
$this->logSaver->warning('Failed to publish to channel', $channel, [
'article_id' => $article->id,
'error' => $e->getMessage(),
]);
return null;
}
}
}