fix: review #5 — ContentSanitizer in all publishers, refresh lock, CheckSocialConnections includes TokenExpired

This commit is contained in:
Paulo Castellano 2026-04-01 12:02:15 -03:00
parent a69dc28298
commit a1df51234b
6 changed files with 50 additions and 31 deletions

View file

@ -19,7 +19,7 @@ public function handle(): void
{
Workspace::query()
->whereHas('socialAccounts', function ($query) {
$query->where('status', Status::Connected);
$query->whereIn('status', [Status::Connected, Status::TokenExpired]);
})
->with('owner')
->chunk(100, function ($workspaces) {

View file

@ -136,7 +136,6 @@ public function markAsTokenExpired(string $errorMessage): void
$this->update([
'status' => Status::TokenExpired,
'error_message' => $errorMessage,
'disconnected_at' => now(),
]);
}

View file

@ -7,6 +7,7 @@
use App\Enums\SocialAccount\Platform;
use App\Exceptions\TokenExpiredException;
use App\Models\SocialAccount;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
@ -46,19 +47,32 @@ public function verify(SocialAccount $account): bool
*/
private function refreshTokenIfNeeded(SocialAccount $account): void
{
match ($account->platform) {
Platform::LinkedIn, Platform::LinkedInPage => $this->refreshLinkedInToken($account),
Platform::X => $this->refreshXToken($account),
Platform::Bluesky => $this->refreshBlueskyToken($account),
Platform::YouTube => $this->refreshYouTubeToken($account),
Platform::TikTok => $this->refreshTikTokToken($account),
Platform::Pinterest => $this->refreshPinterestToken($account),
Platform::Threads => $this->refreshThreadsToken($account),
Platform::Instagram => $this->refreshInstagramToken($account),
// Facebook uses page tokens that don't expire
// Mastodon tokens don't expire
default => null,
};
$lock = Cache::lock("token_refresh:{$account->id}", 30);
if (! $lock->get()) {
// Another process is already refreshing this token
$account->refresh();
return;
}
try {
match ($account->platform) {
Platform::LinkedIn, Platform::LinkedInPage => $this->refreshLinkedInToken($account),
Platform::X => $this->refreshXToken($account),
Platform::Bluesky => $this->refreshBlueskyToken($account),
Platform::YouTube => $this->refreshYouTubeToken($account),
Platform::TikTok => $this->refreshTikTokToken($account),
Platform::Pinterest => $this->refreshPinterestToken($account),
Platform::Threads => $this->refreshThreadsToken($account),
Platform::Instagram => $this->refreshInstagramToken($account),
// Facebook uses page tokens that don't expire
// Mastodon tokens don't expire
default => null,
};
} finally {
$lock->release();
}
}
private function refreshLinkedInToken(SocialAccount $account): void

View file

@ -22,6 +22,8 @@ public function publish(PostPlatform $postPlatform): array
{
$this->validateContentLength($postPlatform);
$content = $postPlatform->content ? app(ContentSanitizer::class)->sanitize($postPlatform->content, $postPlatform->platform) : null;
$account = $postPlatform->socialAccount;
$instance = $account->meta['instance'] ?? 'https://mastodon.social';
@ -38,7 +40,7 @@ public function publish(PostPlatform $postPlatform): array
// Create status
$payload = [
'status' => $postPlatform->content ?? '',
'status' => $content ?? '',
'visibility' => 'public',
];

View file

@ -32,15 +32,17 @@ public function publish(PostPlatform $postPlatform): array
$account->refresh();
}
$content = $postPlatform->content ? app(ContentSanitizer::class)->sanitize($postPlatform->content, $postPlatform->platform) : null;
return match ($postPlatform->content_type) {
ContentType::PinterestPin => $this->publishImagePin($postPlatform),
ContentType::PinterestVideoPin => $this->publishVideoPin($postPlatform),
ContentType::PinterestCarousel => $this->publishCarousel($postPlatform),
ContentType::PinterestPin => $this->publishImagePin($postPlatform, $content),
ContentType::PinterestVideoPin => $this->publishVideoPin($postPlatform, $content),
ContentType::PinterestCarousel => $this->publishCarousel($postPlatform, $content),
default => throw new \Exception("Unsupported content type: {$postPlatform->content_type->value}"),
};
}
private function publishImagePin(PostPlatform $postPlatform): array
private function publishImagePin(PostPlatform $postPlatform, ?string $content): array
{
$account = $postPlatform->socialAccount;
$media = $postPlatform->media->first();
@ -87,8 +89,8 @@ private function publishImagePin(PostPlatform $postPlatform): array
],
];
if ($postPlatform->content) {
$payload['description'] = $postPlatform->content;
if ($content) {
$payload['description'] = $content;
}
if (! empty(data_get($postPlatform->meta, 'title'))) {
@ -122,7 +124,7 @@ private function publishImagePin(PostPlatform $postPlatform): array
];
}
private function publishVideoPin(PostPlatform $postPlatform): array
private function publishVideoPin(PostPlatform $postPlatform, ?string $content): array
{
$account = $postPlatform->socialAccount;
$media = $postPlatform->media->first();
@ -221,8 +223,8 @@ private function publishVideoPin(PostPlatform $postPlatform): array
],
];
if ($postPlatform->content) {
$payload['description'] = $postPlatform->content;
if ($content) {
$payload['description'] = $content;
}
if (! empty(data_get($postPlatform->meta, 'title'))) {
@ -256,7 +258,7 @@ private function publishVideoPin(PostPlatform $postPlatform): array
];
}
private function publishCarousel(PostPlatform $postPlatform): array
private function publishCarousel(PostPlatform $postPlatform, ?string $content): array
{
$account = $postPlatform->socialAccount;
$medias = $postPlatform->media;
@ -283,8 +285,8 @@ private function publishCarousel(PostPlatform $postPlatform): array
],
];
if ($postPlatform->content) {
$payload['description'] = $postPlatform->content;
if ($content) {
$payload['description'] = $content;
}
if (! empty(data_get($postPlatform->meta, 'title'))) {

View file

@ -28,6 +28,8 @@ public function publish(PostPlatform $postPlatform): array
{
$this->validateContentLength($postPlatform);
$content = $postPlatform->content ? app(ContentSanitizer::class)->sanitize($postPlatform->content, $postPlatform->platform) : null;
$account = $postPlatform->socialAccount;
// Refresh token if expired or expiring soon
@ -40,8 +42,8 @@ public function publish(PostPlatform $postPlatform): array
$data = [];
if (! empty($postPlatform->content)) {
$data['text'] = $postPlatform->content;
if (! empty($content)) {
$data['text'] = $content;
}
$mediaIds = [];
@ -65,7 +67,7 @@ public function publish(PostPlatform $postPlatform): array
];
}
if (empty($data['text'] ?? null) && empty($mediaIds)) {
if (empty($content) && empty($mediaIds)) {
throw new \Exception('X posts require either text or media. Please add content to your post.');
}