diff --git a/app/Livewire/NotificationStreams/Forms/CreateNotificationStream.php b/app/Livewire/NotificationStreams/Forms/CreateNotificationStream.php index 20247a96..4c931486 100644 --- a/app/Livewire/NotificationStreams/Forms/CreateNotificationStream.php +++ b/app/Livewire/NotificationStreams/Forms/CreateNotificationStream.php @@ -37,11 +37,11 @@ public function mount(): void /** * Handle changes to the notification stream type. * - * Resets validation for the value field when the type changes. + * Resets validation for the value field and additional fields when the type changes. */ public function updatedFormType(): void { - $this->resetValidation('form.value'); + $this->resetValidation(['form.value', 'form.additional_field_one', 'form.additional_field_two']); } /** @@ -54,14 +54,24 @@ public function submit(): RedirectResponse|Redirector /** @var User $user */ $user = Auth::user(); - NotificationStream::create([ + $data = [ 'label' => $this->form->label, 'type' => $this->form->type, 'value' => $this->form->value, 'receive_successful_backup_notifications' => $this->form->success_notification ? now() : null, 'receive_failed_backup_notifications' => $this->form->failed_notification ? now() : null, 'user_id' => $user->getAttribute('id'), - ]); + ]; + + // Add additional fields if they are set and not null + if (! is_null($this->form->additional_field_one)) { + $data['additional_field_one'] = $this->form->additional_field_one; + } + if (! is_null($this->form->additional_field_two)) { + $data['additional_field_two'] = $this->form->additional_field_two; + } + + NotificationStream::create($data); Toaster::success('Notification stream has been added.'); diff --git a/app/Livewire/NotificationStreams/Forms/NotificationStreamForm.php b/app/Livewire/NotificationStreams/Forms/NotificationStreamForm.php index 945df462..02cbff5e 100644 --- a/app/Livewire/NotificationStreams/Forms/NotificationStreamForm.php +++ b/app/Livewire/NotificationStreams/Forms/NotificationStreamForm.php @@ -18,16 +18,53 @@ class NotificationStreamForm extends Form public string $value = ''; public bool $success_notification = false; public bool $failed_notification = true; + public ?string $additional_field_one = null; + public ?string $additional_field_two = null; /** @var Collection */ public Collection $availableTypes; + /** @var array> */ + protected array $additionalFields = [ + NotificationStream::TYPE_PUSHOVER => [ + 'additional_field_one' => [ + 'label' => 'User Key', + 'rules' => ['required', 'string'], + 'error_message' => 'Please enter a valid Pushover User Key.', + ], + ], + ]; + + /** @var array> */ + protected array $typeConfig = [ + NotificationStream::TYPE_DISCORD => [ + 'label' => 'Webhook URL', + 'input_type' => 'url', + ], + NotificationStream::TYPE_SLACK => [ + 'label' => 'Webhook URL', + 'input_type' => 'url', + ], + NotificationStream::TYPE_TEAMS => [ + 'label' => 'Webhook URL', + 'input_type' => 'url', + ], + NotificationStream::TYPE_EMAIL => [ + 'label' => 'Email Address', + 'input_type' => 'email', + ], + NotificationStream::TYPE_PUSHOVER => [ + 'label' => 'API Token', + 'input_type' => 'text', + ], + ]; + /** * @return array */ public function rules(): array { - return [ + $rules = [ 'label' => ['required', 'string', 'max:255'], 'type' => ['required', 'string', Rule::in($this->availableTypes->keys())], 'success_notification' => ['nullable', 'boolean'], @@ -49,6 +86,18 @@ function ($attribute, $value, $fail): void { }, ], ]; + + // Add dynamic rules for additional fields + if (isset($this->additionalFields[$this->type])) { + foreach ($this->additionalFields[$this->type] as $field => $config) { + $rules[$field] = $config['rules']; + } + } else { + $rules['additional_field_one'] = ['nullable']; + $rules['additional_field_two'] = ['nullable']; + } + + return $rules; } /** @@ -56,7 +105,7 @@ function ($attribute, $value, $fail): void { */ public function messages(): array { - return [ + $messages = [ 'label.required' => __('Please enter a label.'), 'label.max' => __('The label must not exceed 255 characters.'), 'type.required' => __('Please select a notification type.'), @@ -65,6 +114,14 @@ public function messages(): array 'success_notification.boolean' => __('The notification status must be true or false.'), 'failed_notification.boolean' => __('The notification status must be true or false.'), ]; + + if (isset($this->additionalFields[$this->type])) { + foreach ($this->additionalFields[$this->type] as $field => $config) { + $messages[$field . '.required'] = __($config['error_message']); + } + } + + return $messages; } public function initialize(): void @@ -85,6 +142,28 @@ public function setNotificationStream(NotificationStream $notificationStream): v $this->value = $notificationStream->getAttribute('value'); $this->success_notification = (bool) $notificationStream->getAttribute('receive_successful_backup_notifications'); $this->failed_notification = (bool) $notificationStream->getAttribute('receive_failed_backup_notifications'); + $this->additional_field_one = $notificationStream->getAttribute('additional_field_one'); + $this->additional_field_two = $notificationStream->getAttribute('additional_field_two'); + } + + /** + * Get the configuration for additional fields based on the current type. + * + * @return array> + */ + public function getAdditionalFieldsConfig(): array + { + return $this->additionalFields[$this->type] ?? []; + } + + public function getValueLabel(): string + { + return $this->typeConfig[$this->type]['label'] ?? __('Value'); + } + + public function getValueInputType(): string + { + return $this->typeConfig[$this->type]['input_type'] ?? 'text'; } /** @@ -96,7 +175,7 @@ protected function getValueValidationRule(): array|string NotificationStream::TYPE_DISCORD => ['url', 'regex:/^https:\/\/discord\.com\/api\/webhooks\//'], NotificationStream::TYPE_SLACK => ['url', 'regex:/^https:\/\/hooks\.slack\.com\/services\//'], NotificationStream::TYPE_TEAMS => ['url', 'regex:/^https:\/\/.*\.webhook\.office\.com\/webhookb2\/.+/i'], - NotificationStream::TYPE_PUSHOVER => ['string'], + NotificationStream::TYPE_PUSHOVER => ['required', 'string'], NotificationStream::TYPE_EMAIL => ['email'], default => 'string', }; @@ -105,11 +184,11 @@ protected function getValueValidationRule(): array|string protected function getValueErrorMessage(): string { return match ($this->type) { - NotificationStream::TYPE_DISCORD => __('Please enter a Discord webhook URL.'), - NotificationStream::TYPE_SLACK => __('Please enter a Slack webhook URL.'), - NotificationStream::TYPE_TEAMS => __('Please enter a Microsoft Teams Webhook URL.'), - NotificationStream::TYPE_EMAIL => __('Please enter an email address.'), - NotificationStream::TYPE_PUSHOVER => __('Please enter a Pushover token.'), + NotificationStream::TYPE_DISCORD => __('Please enter a valid Discord webhook URL.'), + NotificationStream::TYPE_SLACK => __('Please enter a valid Slack webhook URL.'), + NotificationStream::TYPE_TEAMS => __('Please enter a valid Microsoft Teams Webhook URL.'), + NotificationStream::TYPE_EMAIL => __('Please enter a valid email address.'), + NotificationStream::TYPE_PUSHOVER => __('Please enter a valid Pushover API Token.'), default => __('Please enter a valid value for the selected notification type.'), }; } diff --git a/app/Livewire/NotificationStreams/Forms/UpdateNotificationStream.php b/app/Livewire/NotificationStreams/Forms/UpdateNotificationStream.php index b4cff8f9..bf0c65d9 100644 --- a/app/Livewire/NotificationStreams/Forms/UpdateNotificationStream.php +++ b/app/Livewire/NotificationStreams/Forms/UpdateNotificationStream.php @@ -45,11 +45,11 @@ public function mount(NotificationStream $notificationStream): void /** * Handle changes to the notification stream type. * - * Resets validation for the value field when the type changes. + * Resets validation for the value field and additional fields when the type changes. */ public function updatedFormType(): void { - $this->resetValidation('form.value'); + $this->resetValidation(['form.value', 'form.additional_field_one', 'form.additional_field_two']); } /** @@ -61,13 +61,25 @@ public function submit(): RedirectResponse|Redirector $this->form->validate(); - $this->notificationStream->update([ + $data = [ 'label' => $this->form->label, 'type' => $this->form->type, 'value' => $this->form->value, 'receive_successful_backup_notifications' => $this->form->success_notification ? now() : null, 'receive_failed_backup_notifications' => $this->form->failed_notification ? now() : null, - ]); + ]; + + // Handle additional fields + if ($this->form->getAdditionalFieldsConfig()) { + $data['additional_field_one'] = $this->form->additional_field_one; + $data['additional_field_two'] = $this->form->additional_field_two; + } else { + // If the type doesn't use additional fields, set them to null + $data['additional_field_one'] = null; + $data['additional_field_two'] = null; + } + + $this->notificationStream->update($data); Toaster::success('Notification stream has been updated.'); diff --git a/database/migrations/2024_08_03_082049_add_additional_fields_to_notification_streams_table.php b/database/migrations/2024_08_03_082049_add_additional_fields_to_notification_streams_table.php new file mode 100644 index 00000000..31ddc759 --- /dev/null +++ b/database/migrations/2024_08_03_082049_add_additional_fields_to_notification_streams_table.php @@ -0,0 +1,16 @@ +text('additional_field_one')->nullable(); + $table->text('additional_field_two')->nullable(); + }); + } +}; diff --git a/resources/views/components/notification-stream-form.blade.php b/resources/views/components/notification-stream-form.blade.php index 2d604cae..c27bfc40 100644 --- a/resources/views/components/notification-stream-form.blade.php +++ b/resources/views/components/notification-stream-form.blade.php @@ -27,10 +27,19 @@
- - + + @error('form.value') @enderror
+ + @foreach ($form->getAdditionalFieldsConfig() as $field => $config) +
+ + + @error('form.' . $field) @enderror +
+ @endforeach +
{{ __('Notifications') }} diff --git a/tests/Feature/NotificationStreams/Livewire/Forms/CreateNotificationStreamTest.php b/tests/Feature/NotificationStreams/Livewire/Forms/CreateNotificationStreamTest.php index 3056e51b..e1f70003 100644 --- a/tests/Feature/NotificationStreams/Livewire/Forms/CreateNotificationStreamTest.php +++ b/tests/Feature/NotificationStreams/Livewire/Forms/CreateNotificationStreamTest.php @@ -409,3 +409,49 @@ Carbon::setTestNow(); }); + +it('submits successfully with Pushover and additional fields', function (): void { + $testData = [ + 'label' => 'Test Pushover Notification', + 'type' => 'pushover', + 'value' => 'azGDORePK8gMaC0QOYAMyEEuzJnyUi', // Example Pushover API Token + 'additional_field_one' => 'uQiRzpo4DXghDmr9QzzfQu27cmVRsG', // Example User Key + 'success_notification' => true, + 'failed_notification' => true, + ]; + + Livewire::actingAs($this->user) + ->test(CreateNotificationStream::class) + ->set('form.label', $testData['label']) + ->set('form.type', $testData['type']) + ->set('form.value', $testData['value']) + ->set('form.additional_field_one', $testData['additional_field_one']) + ->set('form.success_notification', $testData['success_notification']) + ->set('form.failed_notification', $testData['failed_notification']) + ->call('submit') + ->assertHasNoErrors() + ->assertRedirect(route('notification-streams.index')); + + $this->assertDatabaseHas('notification_streams', [ + 'user_id' => $this->user->id, + 'label' => $testData['label'], + 'type' => $testData['type'], + 'value' => $testData['value'], + 'additional_field_one' => $testData['additional_field_one'], + ]); + + $notificationStream = $this->user->notificationStreams()->latest()->first(); + $this->assertNotNull($notificationStream->receive_successful_backup_notifications); + $this->assertNotNull($notificationStream->receive_failed_backup_notifications); +}); + +it('validates Pushover additional fields', function (): void { + Livewire::actingAs($this->user) + ->test(CreateNotificationStream::class) + ->set('form.label', 'Test Pushover') + ->set('form.type', 'pushover') + ->set('form.value', 'validApiToken') + ->set('form.additional_field_one', '') // Empty User Key + ->call('submit') + ->assertHasErrors(['form.additional_field_one']); +}); diff --git a/tests/Feature/NotificationStreams/Livewire/Forms/UpdateNotificationStreamTest.php b/tests/Feature/NotificationStreams/Livewire/Forms/UpdateNotificationStreamTest.php index 68634baa..0d4bfb7d 100644 --- a/tests/Feature/NotificationStreams/Livewire/Forms/UpdateNotificationStreamTest.php +++ b/tests/Feature/NotificationStreams/Livewire/Forms/UpdateNotificationStreamTest.php @@ -375,3 +375,64 @@ ->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream]) ->assertStatus(403); }); + +it('updates successfully with Pushover and additional fields', function (): void { + $user = User::factory()->create(); + $notificationStream = NotificationStream::factory()->create([ + 'user_id' => $user->id, + 'type' => 'pushover', + 'value' => 'oldApiToken', + 'additional_field_one' => 'oldUserKey', + 'receive_successful_backup_notifications' => null, + 'receive_failed_backup_notifications' => now(), + ]); + + $newData = [ + 'label' => 'Updated Pushover Notification', + 'type' => 'pushover', + 'value' => 'newApiToken', + 'additional_field_one' => 'newUserKey', + 'success_notification' => true, + 'failed_notification' => false, + ]; + + Livewire::actingAs($user) + ->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream]) + ->set('form.label', $newData['label']) + ->set('form.type', $newData['type']) + ->set('form.value', $newData['value']) + ->set('form.additional_field_one', $newData['additional_field_one']) + ->set('form.success_notification', $newData['success_notification']) + ->set('form.failed_notification', $newData['failed_notification']) + ->call('submit') + ->assertHasNoErrors() + ->assertRedirect(route('notification-streams.index')); + + $updatedStream = $notificationStream->fresh(); + expect($updatedStream)->toMatchArray([ + 'id' => $notificationStream->id, + 'user_id' => $user->id, + 'label' => $newData['label'], + 'type' => $newData['type'], + 'value' => $newData['value'], + 'additional_field_one' => $newData['additional_field_one'], + ]) + ->and($updatedStream->receive_successful_backup_notifications)->not->toBeNull() + ->and($updatedStream->receive_failed_backup_notifications)->toBeNull(); +}); + +it('validates Pushover additional fields during update', function (): void { + $user = User::factory()->create(); + $notificationStream = NotificationStream::factory()->create([ + 'user_id' => $user->id, + 'type' => 'pushover', + 'value' => 'validApiToken', + 'additional_field_one' => 'validUserKey', + ]); + + Livewire::actingAs($user) + ->test(UpdateNotificationStream::class, ['notificationStream' => $notificationStream]) + ->set('form.additional_field_one', '') // Empty User Key + ->call('submit') + ->assertHasErrors(['form.additional_field_one']); +}); diff --git a/tests/Unit/Models/BackupTaskTest.php b/tests/Unit/Models/BackupTaskTest.php index 44aac472..82c4710e 100644 --- a/tests/Unit/Models/BackupTaskTest.php +++ b/tests/Unit/Models/BackupTaskTest.php @@ -921,29 +921,6 @@ Http::assertSent(fn ($request): bool => $request->url() === 'https://api.pushover.net/1/messages.json'); }); -it('sends a Pushover notification for failed backup', function (): void { - $task = BackupTask::factory()->create(); - $log = BackupTaskLog::factory()->create([ - 'backup_task_id' => $task->id, - 'successful_at' => null, - ]); - $pushoverToken = 'abc123'; - - Http::fake([ - 'https://api.pushover.net/1/messages.json' => Http::response('', 200), - ]); - - $task->sendPushoverNotification($log, $pushoverToken); - - Http::assertSent(function (array $request) use ($pushoverToken, $task): bool { - return $request->url() === 'https://api.pushover.net/1/messages.json' - && $request['token'] === $pushoverToken - && $request['title'] === "{$task->label} Backup Task: Failure" - && str_contains((string) $request['message'], 'The backup task failed.') - && $request['priority'] === 1; - }); -}); - it('throws an exception when Pushover notification fails', function (): void { $task = BackupTask::factory()->create(); $log = BackupTaskLog::factory()->create(['backup_task_id' => $task->id]);