Threads posts over 500 chars were saved + scheduled successfully and only failed inside the publish job. The frontend already showed the 537|500 badge but `canSchedule` ignored content length, so Schedule and Post Now stayed enabled. Backend `UpdatePostRequest` only capped at 63206 (Facebook's max), not per-platform. - Add `Platform::contentOverflow()` as the single source of truth and reuse it from `HasSocialHttpClient::validateContentLength` (publish-time). - New `ContentFitsPlatformLimits` rule applied to the `content` field on `App\\UpdatePostRequest`, `Api\\UpdatePostRequest`, and `Api\\StorePostRequest` via `Rule::when(...)` so drafts are not blocked. - Rule dedupes per platform (two Threads accounts -> one error) and reports the platform label, hard cap, and overage via i18n. - Edit.vue feeds `contentLengthOverflows` into `canSchedule` and lists each offending platform in `postActionTooltip` using the existing `getPlatformLabel` resolver.
78 lines
2 KiB
PHP
78 lines
2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Services\Social\Concerns;
|
|
|
|
use App\Models\PostPlatform;
|
|
use App\Models\SocialAccount;
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
trait HasSocialHttpClient
|
|
{
|
|
protected function validateContentLength(PostPlatform $postPlatform): void
|
|
{
|
|
$content = $postPlatform->post->content ?? '';
|
|
|
|
if ($postPlatform->platform->contentOverflow($content) === null) {
|
|
return;
|
|
}
|
|
|
|
$maxLength = $postPlatform->platform->maxContentLength();
|
|
$contentLength = mb_strlen($content);
|
|
|
|
throw new \Exception(
|
|
"Content exceeds {$postPlatform->platform->label()} limit of {$maxLength} characters ({$contentLength} provided)."
|
|
);
|
|
}
|
|
|
|
protected function refreshTokenWithLock(SocialAccount $account, callable $refreshFn): void
|
|
{
|
|
$lock = Cache::lock("token_refresh:{$account->id}", 30);
|
|
|
|
if (! $lock->get()) {
|
|
// Another process is refreshing, wait and reload
|
|
sleep(2);
|
|
$account->refresh();
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$refreshFn();
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
protected function socialHttp(): PendingRequest
|
|
{
|
|
return Http::retry(
|
|
times: 3,
|
|
sleepMilliseconds: 5000,
|
|
when: fn ($exception, $request) => $exception->response?->status() === 429,
|
|
throw: false,
|
|
)->timeout(120);
|
|
}
|
|
|
|
protected function redactResponseBody(string $body): string
|
|
{
|
|
return preg_replace(
|
|
[
|
|
'/access_token=([^&"\s]+)/',
|
|
'/"access_token"\s*:\s*"([^"]+)"/',
|
|
'/Bearer\s+\S+/',
|
|
'/"token"\s*:\s*"([^"]+)"/',
|
|
],
|
|
[
|
|
'access_token=[REDACTED]',
|
|
'"access_token":"[REDACTED]"',
|
|
'Bearer [REDACTED]',
|
|
'"token":"[REDACTED]"',
|
|
],
|
|
$body
|
|
);
|
|
}
|
|
}
|