Handle Woo plugin connection timeouts without 500 errors.
Deploy Ladill Woo Manager / deploy (push) Successful in 1m4s

Treat unreachable WordPress stores as unavailable during integrations refresh instead of throwing when the site times out.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
isaacclad
2026-07-08 11:02:06 +00:00
co-authored by Cursor
parent 09359ca86a
commit 1e97f129e9
3 changed files with 70 additions and 12 deletions
@@ -36,6 +36,8 @@ class IntegrationController extends Controller
return redirect()
->route('woo.integrations.index')
->with('status', 'Integration status refreshed.');
->with('success', $store
? 'Integration status refreshed.'
: 'Connect a store to refresh integration status.');
}
}
+40 -11
View File
@@ -3,6 +3,7 @@
namespace App\Services\Woo;
use App\Models\WooStore;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Http\Client\Response;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\Http;
@@ -30,7 +31,13 @@ class PluginClient
public function delete(WooStore $store, string $path): bool
{
return $this->raw($store, 'DELETE', $path)->successful();
try {
return $this->raw($store, 'DELETE', $path)->successful();
} catch (ConnectionException $e) {
$this->logConnectionFailure($store, 'DELETE', $path, $e);
return false;
}
}
/** @return array<string, mixed>|null */
@@ -39,15 +46,21 @@ class PluginClient
$site = rtrim((string) $store->site_url, '/');
$url = $site.'/wp-json/ladill-woo/v1/media';
$response = Http::acceptJson()
->timeout(60)
->withHeaders(['X-Ladill-Store' => $store->public_id])
->attach(
'file',
file_get_contents($file->getRealPath()) ?: '',
$file->getClientOriginalName() ?: 'upload.jpg',
)
->post($url, array_filter(['alt' => $alt !== '' ? $alt : null]));
try {
$response = Http::acceptJson()
->timeout(60)
->withHeaders(['X-Ladill-Store' => $store->public_id])
->attach(
'file',
file_get_contents($file->getRealPath()) ?: '',
$file->getClientOriginalName() ?: 'upload.jpg',
)
->post($url, array_filter(['alt' => $alt !== '' ? $alt : null]));
} catch (ConnectionException $e) {
$this->logConnectionFailure($store, 'POST', 'media', $e);
return null;
}
if (! $response->successful()) {
Log::warning('Woo plugin media upload failed', [
@@ -67,7 +80,13 @@ class PluginClient
/** @return array<string, mixed>|null */
private function request(WooStore $store, string $method, string $path, array $data = []): ?array
{
$response = $this->raw($store, $method, $path, $data);
try {
$response = $this->raw($store, $method, $path, $data);
} catch (ConnectionException $e) {
$this->logConnectionFailure($store, $method, $path, $e);
return null;
}
if (! $response->successful()) {
Log::warning('Woo plugin API request failed', [
@@ -103,4 +122,14 @@ class PluginClient
default => throw new \InvalidArgumentException("Unsupported method: {$method}"),
};
}
private function logConnectionFailure(WooStore $store, string $method, string $path, ConnectionException $e): void
{
Log::warning('Woo plugin API connection failed', [
'store_id' => $store->id,
'method' => $method,
'path' => $path,
'message' => $e->getMessage(),
]);
}
}
+27
View File
@@ -143,4 +143,31 @@ class WooIntegrationsTest extends TestCase
Http::assertSentCount(2);
$this->assertTrue(Cache::has('woo.integrations.'.$store->id));
}
public function test_integrations_refresh_survives_store_connection_timeout(): void
{
$user = User::factory()->create();
WooStore::create([
'user_id' => $user->id,
'site_url' => 'https://shop.example.com',
'site_name' => 'Example Shop',
'status' => WooStore::STATUS_ACTIVE,
'connected_at' => now(),
'public_id' => 'store-public-id',
]);
Http::fake([
'https://shop.example.com/wp-json/ladill-woo/v1/integrations/status' => function () {
throw new \Illuminate\Http\Client\ConnectionException(
new \Exception('cURL error 28: Operation timed out')
);
},
]);
$this->actingAs($user)
->from(route('woo.integrations.index'))
->post(route('woo.integrations.refresh'))
->assertRedirect(route('woo.integrations.index'))
->assertSessionHas('success');
}
}