diff --git a/app/Models/Plugin.php b/app/Models/Plugin.php index d476aea..abd10b8 100644 --- a/app/Models/Plugin.php +++ b/app/Models/Plugin.php @@ -56,9 +56,19 @@ class Plugin extends Model } } - $response = Http::withHeaders($headers) - ->get($this->polling_url) - ->json(); + $httpRequest = Http::withHeaders($headers); + + // Add body for POST requests if polling_body is provided + if ($this->polling_verb === 'post' && $this->polling_body) { + $httpRequest = $httpRequest->withBody($this->polling_body); + } + + // Make the request based on the verb + if ($this->polling_verb === 'post') { + $response = $httpRequest->post($this->polling_url)->json(); + } else { + $response = $httpRequest->get($this->polling_url)->json(); + } $this->update([ 'data_payload' => $response, diff --git a/database/factories/PluginFactory.php b/database/factories/PluginFactory.php index a4aa033..a2d2e65 100644 --- a/database/factories/PluginFactory.php +++ b/database/factories/PluginFactory.php @@ -22,6 +22,7 @@ class PluginFactory extends Factory 'polling_url' => $this->faker->url(), 'polling_verb' => $this->faker->randomElement(['get', 'post']), 'polling_header' => null, + 'polling_body' => null, 'render_markup' => null, 'render_markup_view' => null, 'detail_view_route' => null, diff --git a/database/migrations/2025_06_20_163742_allow_long_polling_url.php b/database/migrations/2025_06_20_163742_allow_long_polling_url.php index b29eebc..867837f 100644 --- a/database/migrations/2025_06_20_163742_allow_long_polling_url.php +++ b/database/migrations/2025_06_20_163742_allow_long_polling_url.php @@ -11,7 +11,7 @@ return new class extends Migration */ public function up(): void { - Schema::table("plugins", function (Blueprint $table) { + Schema::table('plugins', function (Blueprint $table) { $table->string('polling_url', 1024)->nullable()->change(); }); } @@ -21,7 +21,7 @@ return new class extends Migration */ public function down(): void { - Schema::table("plugins", function (Blueprint $table) { + Schema::table('plugins', function (Blueprint $table) { // old default string length value in Illuminate $table->string('polling_url', 255)->nullable()->change(); }); diff --git a/database/migrations/2025_07_02_161953_add_polling_body_to_plugins_table.php b/database/migrations/2025_07_02_161953_add_polling_body_to_plugins_table.php new file mode 100644 index 0000000..c1fbc94 --- /dev/null +++ b/database/migrations/2025_07_02_161953_add_polling_body_to_plugins_table.php @@ -0,0 +1,28 @@ +text('polling_body')->nullable()->after('polling_header'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('plugins', function (Blueprint $table) { + $table->dropColumn('polling_body'); + }); + } +}; diff --git a/resources/views/livewire/plugins/index.blade.php b/resources/views/livewire/plugins/index.blade.php index eace5ba..241ff57 100644 --- a/resources/views/livewire/plugins/index.blade.php +++ b/resources/views/livewire/plugins/index.blade.php @@ -10,6 +10,7 @@ new class extends Component { public string $polling_url; public string $polling_verb = "get"; public $polling_header; + public $polling_body; public array $plugins; public array $native_plugins = [ @@ -26,6 +27,7 @@ new class extends Component { 'polling_url' => 'required_if:data_strategy,polling|nullable|url', 'polling_verb' => 'required|string|in:get,post', 'polling_header' => 'nullable|string|max:255', + 'polling_body' => 'nullable|string', ]; private function refreshPlugins(): void @@ -56,9 +58,10 @@ new class extends Component { 'polling_url' => $this->polling_url ?? null, 'polling_verb' => $this->polling_verb, 'polling_header' => $this->polling_header, + 'polling_body' => $this->polling_body, ]); - $this->reset(['name', 'data_stale_minutes', 'data_strategy', 'polling_url', 'polling_verb', 'polling_header']); + $this->reset(['name', 'data_stale_minutes', 'data_strategy', 'polling_url', 'polling_verb', 'polling_header', 'polling_body']); $this->refreshPlugins(); Flux::modal('add-plugin')->close(); @@ -131,7 +134,7 @@ new class extends Component {
- + @@ -141,6 +144,20 @@ new class extends Component {
+ + @if($polling_verb === 'post') +
+ +
+ @endif
polling_url = $this->plugin->polling_url; $this->polling_verb = $this->plugin->polling_verb; $this->polling_header = $this->plugin->polling_header; + $this->polling_body = $this->plugin->polling_body; $this->data_payload = json_encode($this->plugin->data_payload, JSON_PRETTY_PRINT); } @@ -81,6 +83,7 @@ new class extends Component { 'polling_url' => 'required_if:data_strategy,polling|nullable|url', 'polling_verb' => 'required|string|in:get,post', 'polling_header' => 'nullable|string|max:255', + 'polling_body' => 'nullable|string', 'data_payload' => 'required_if:data_strategy,static|nullable|json', 'blade_code' => 'nullable|string', 'checked_devices' => 'array', @@ -95,12 +98,8 @@ new class extends Component { { abort_unless(auth()->user()->plugins->contains($this->plugin), 403); $validated = $this->validate(); - - // If static strategy is selected, parse and save the JSON data - if ($this->data_strategy === 'static' && isset($validated['data_payload'])) { - $validated['data_payload'] = json_decode($validated['data_payload'], true); - } - + $validated['data_payload'] = json_decode($validated['data_payload'], true); + $this->plugin->update($validated); } @@ -120,9 +119,20 @@ new class extends Component { } } - $response = Http::withHeaders($headers) - ->get($this->plugin->polling_url) - ->json(); + // Prepare the HTTP request + $httpRequest = Http::withHeaders($headers); + + // Add body for POST requests if polling_body is provided + if ($this->plugin->polling_verb === 'post' && $this->plugin->polling_body) { + $httpRequest = $httpRequest->withBody($this->plugin->polling_body, 'application/json'); + } + + // Make the request based on the verb + if ($this->plugin->polling_verb === 'post') { + $response = $httpRequest->post($this->plugin->polling_url)->json(); + } else { + $response = $httpRequest->get($this->plugin->polling_url)->json(); + } $this->plugin->update([ 'data_payload' => $response, @@ -480,7 +490,7 @@ HTML;
- + @@ -497,6 +507,19 @@ HTML; placeholder="Authorization: Bearer ey.******* Content-Type: application/json" />
+ + @if($polling_verb === 'post') +
+ +
+ @endif
data_payload_updated_at) {{ $this->data_payload_updated_at?->diffForHumans() ?? 'Never' }} @endisset +
- -
+ +

Markup

@if($plugin->render_markup_view)
diff --git a/tests/Unit/Models/PluginTest.php b/tests/Unit/Models/PluginTest.php index 19b09a0..173c5a8 100644 --- a/tests/Unit/Models/PluginTest.php +++ b/tests/Unit/Models/PluginTest.php @@ -1,6 +1,7 @@ toBeArray() ->toBe($data); }); + +test('plugin can have polling body for POST requests', function () { + $plugin = Plugin::factory()->create([ + 'polling_verb' => 'post', + 'polling_body' => '{"query": "query { user { id name } }"}', + ]); + + expect($plugin->polling_body)->toBe('{"query": "query { user { id name } }"}'); +}); + +test('updateDataPayload sends POST request with body when polling_verb is post', function () { + Http::fake([ + 'https://example.com/api' => Http::response(['success' => true], 200), + ]); + + $plugin = Plugin::factory()->create([ + 'data_strategy' => 'polling', + 'polling_url' => 'https://example.com/api', + 'polling_verb' => 'post', + 'polling_body' => '{"query": "query { user { id name } }"}', + ]); + + $plugin->updateDataPayload(); + + Http::assertSent(function ($request) { + return $request->url() === 'https://example.com/api' && + $request->method() === 'POST' && + $request->body() === '{"query": "query { user { id name } }"}'; + }); +});