Expand test notification framework.

This commit is contained in:
James Cole
2024-12-08 16:28:22 +01:00
parent c06fb8daf6
commit 2f7a1c941e
15 changed files with 509 additions and 70 deletions

View File

@@ -1,8 +1,7 @@
<?php
/**
* AdminRequestedTestMessage.php
* Copyright (c) 2019 james@firefly-iii.org
/*
* TestEmailChannel.php
* Copyright (c) 2024 james@firefly-iii.org.
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -17,31 +16,30 @@
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Test;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class AdminRequestedTestMessage.
*/
class AdminRequestedTestMessage extends Event
class TestNotificationChannel
{
use SerializesModels;
public User $user;
public User $user;
public string $channel;
/**
* Create a new event instance.
*/
public function __construct(User $user)
public function __construct(string $channel, User $user)
{
app('log')->debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s)', $user->id, $user->email));
$this->user = $user;
app('log')->debug(sprintf('Triggered TestNotificationChannel("%s") for user #%d (%s)', $channel, $user->id, $user->email));
$this->user = $user;
$this->channel = $channel;
}
}

View File

@@ -26,10 +26,15 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Events\NewVersionAvailable;
use FireflyIII\Events\Test\TestNotificationChannel;
use FireflyIII\Notifications\Admin\TestNotification;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Test\TestNotificationDiscord;
use FireflyIII\Notifications\Test\TestNotificationEmail;
use FireflyIII\Notifications\Test\TestNotificationSlack;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
/**
@@ -39,7 +44,7 @@ class AdminEventHandler
{
public function sendInvitationNotification(InvitationCreated $event): void
{
$sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data;
$sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data;
if (false === $sendMail) {
return;
}
@@ -75,7 +80,7 @@ class AdminEventHandler
*/
public function sendNewVersion(NewVersionAvailable $event): void
{
$sendMail = app('fireflyconfig')->get('notification_new_version', true)->data;
$sendMail = app('fireflyconfig')->get('notification_new_version', true)->data;
if (false === $sendMail) {
return;
}
@@ -109,17 +114,34 @@ class AdminEventHandler
/**
* Sends a test message to an administrator.
*/
public function sendTestMessage(AdminRequestedTestMessage $event): void
public function sendTestNotification(TestNotificationChannel $event): void
{
Log::debug(sprintf('Now in sendTestNotification(#%d, "%s")', $event->user->id, $event->channel));
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
if (!$repository->hasRole($event->user, 'owner')) {
Log::error(sprintf('User #%d is not an owner.', $event->user->id));
return;
}
switch($event->channel) {
case 'email':
$class = TestNotificationEmail::class;
break;
case 'slack':
$class = TestNotificationSlack::class;
break;
case 'discord':
$class = TestNotificationDiscord::class;
break;
default:
app('log')->error(sprintf('Unknown channel "%s" in sendTestNotification method.', $event->channel));
return;
}
Log::debug(sprintf('Will send %s as a notification.', $class));
try {
Notification::send($event->user, new TestNotification($event->user->email));
Notification::send($event->user, new $class($event->user->email));
} catch (\Exception $e) { // @phpstan-ignore-line
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
@@ -135,5 +157,6 @@ class AdminEventHandler
app('log')->error($e->getMessage());
app('log')->error($e->getTraceAsString());
}
Log::debug(sprintf('If you see no errors above this line, test notification was sent over channel "%s"', $event->channel));
}
}

View File

@@ -67,23 +67,4 @@ class HomeController extends Controller
return view('admin.index', compact('title', 'mainTitleIcon', 'email'));
}
/**
* Send a test message to the admin.
*
* @return Redirector|RedirectResponse
*/
public function testMessage()
{
die('disabled.');
Log::channel('audit')->info('User sends test message.');
/** @var User $user */
$user = auth()->user();
app('log')->debug('Now in testMessage() controller.');
event(new AdminRequestedTestMessage($user));
session()->flash('info', (string)trans('firefly.send_test_triggered'));
return redirect(route('admin.index'));
}
}

View File

@@ -23,9 +23,12 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\Test\TestNotificationChannel;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\NotificationRequest;
use FireflyIII\User;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
class NotificationController extends Controller
@@ -38,30 +41,69 @@ class NotificationController extends Controller
$subTitle = (string) trans('firefly.title_owner_notifications');
$subTitleIcon = 'envelope-o';
$slackUrl = app('fireflyconfig')->get('slack_webhook_url', '')->data;
$discordUrl = app('fireflyconfig')->get('discord_webhook_url', '')->data;
$discordUrl = app('fireflyconfig')->get('discord_webhook_url', '')->data;
$channels = config('notifications.channels');
// admin notification settings:
$notifications = [];
foreach (config('notifications.notifications.owner') as $key => $info) {
if($info['enabled']) {
if ($info['enabled']) {
$notifications[$key] = app('fireflyconfig')->get(sprintf('notification_%s', $key), true)->data;
}
}
return view('admin.notifications.index', compact('title', 'subTitle', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl','discordUrl','notifications'));
return view('admin.notifications.index', compact('title', 'subTitle', 'mainTitleIcon', 'subTitleIcon', 'channels', 'slackUrl', 'discordUrl', 'notifications'));
}
public function postIndex(NotificationRequest $request): RedirectResponse {
public function postIndex(NotificationRequest $request): RedirectResponse
{
$all = $request->getAll();
var_dump($request->getAll());
exit;
// app('fireflyconfig')->set(sprintf('notification_%s', $key), $value);;
foreach (config('notifications.notifications.owner') as $key => $info) {
if (array_key_exists($key, $all)) {
app('fireflyconfig')->set(sprintf('notification_%s', $key), $all[$key]);
}
}
if ('' === $all['slack_url']) {
app('fireflyconfig')->delete('slack_webhook_url');
}
if ('' === $all['discord_url']) {
app('fireflyconfig')->delete('discord_webhook_url');
}
if ('' !== $all['slack_url']) {
app('fireflyconfig')->set('slack_webhook_url', $all['slack_url']);
}
if ('' !== $all['discord_url']) {
app('fireflyconfig')->set('discord_webhook_url', $all['discord_url']);
}
session()->flash('success', (string)trans('firefly.notification_settings_saved'));
session()->flash('success', (string) trans('firefly.notification_settings_saved'));
return redirect(route('admin.index'));
return redirect(route('admin.notification.index'));
}
public function testNotification(Request $request): RedirectResponse
{
$all = $request->all();
$channel = $all['test_submit'] ?? '';
switch ($channel) {
default:
session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));
break;
case 'email':
case 'discord':
case 'slack':
/** @var User $user */
$user = auth()->user();
app('log')->debug(sprintf('Now in testNotification("%s") controller.', $channel));
event(new TestNotificationChannel($channel, $user));
session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
}
return redirect(route('admin.notification.index'));
}
}

View File

@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Requests;
use FireflyIII\Rules\Admin\IsValidDiscordUrl;
use FireflyIII\Rules\Admin\IsValidSlackUrl;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use Illuminate\Foundation\Http\FormRequest;
@@ -42,18 +44,9 @@ class NotificationRequest extends FormRequest
}
$return[$key] = $value;
}
$return['discord_url'] = $this->convertString('discordUrl');
$return['slack_url'] = $this->convertString('slackUrl');
$return['discord_url'] = $this->convertString('discord_url');
$return['slack_url'] = $this->convertString('slack_url');
return $return;
// if (UrlValidator::isValidWebhookURL($url)) {
// app('fireflyconfig')->set('slack_webhook_url', $url);
// }
// }
//
//
// var_dump($this->all());
// exit;
// return [];
}
/**
@@ -61,10 +54,14 @@ class NotificationRequest extends FormRequest
*/
public function rules(): array
{
// fixed
return [
//'password' => 'required',
$rules = [
'discord_url' => ['nullable', 'url', 'min:1', new IsValidDiscordUrl()],
'slack_url' => ['nullable', 'url', 'min:1', new IsValidSlackUrl()],
];
foreach (config('notifications.notifications.owner') as $key => $info) {
$rules[sprintf('notification_%s', $key)] = 'in:0,1';
}
return $rules;
}
}

View File

@@ -170,15 +170,11 @@ class TransactionJournal extends Model
public function scopeAfter(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
Log::debug(sprintf('scopeAfter("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '>=', $date->format('Y-m-d H:i:s'));
}
public function scopeBefore(EloquentBuilder $query, Carbon $date): EloquentBuilder
{
Log::debug(sprintf('scopeBefore("%s")', $date->format('Y-m-d H:i:s')));
return $query->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s'));
}

View File

@@ -0,0 +1,108 @@
<?php
/*
* TestNotification.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
/**
* Class TestNotification
*/
class TestNotificationDiscord extends Notification
{
use Queueable;
private string $address;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
{
$this->address = $address;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {
// since it's an admin notificaiton, grab the URL from fireflyconfig
$url = app('fireflyconfig')->get('discord_webhook_url', '')->data;
return (new SlackMessage())
->content((string)trans('email.admin_test_subject'))
->to($url);
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
}

View File

@@ -0,0 +1,102 @@
<?php
/*
* TestNotification.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification;
/**
* Class TestNotification
*/
class TestNotificationEmail extends Notification
{
use Queueable;
private string $address;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
{
$this->address = $address;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
return (new MailMessage())
->markdown('emails.admin-test', ['email' => $this->address])
->subject((string) trans('email.admin_test_subject'));
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
{
return ['mail'];
}
}

View File

@@ -0,0 +1,113 @@
<?php
/*
* TestNotification.php
* Copyright (c) 2022 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Notifications\Test;
use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification;
//use Illuminate\Notifications\Slack\SlackMessage;
/**
* Class TestNotification
*/
class TestNotificationSlack extends Notification
{
use Queueable;
private string $address;
/**
* Create a new notification instance.
*/
public function __construct(string $address)
{
$this->address = $address;
}
/**
* Get the array representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return array
*/
public function toArray($notifiable)
{
return [
];
}
/**
* Get the mail representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @return MailMessage
*/
public function toMail($notifiable)
{
}
/**
* Get the Slack representation of the notification.
*
* @param mixed $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/
public function toSlack($notifiable) {
// since it's an admin notification, grab the URL from fireflyconfig
$url = app('fireflyconfig')->get('slack_webhook_url', '')->data;
// return (new SlackMessage)
// ->text((string)trans('email.admin_test_subject'))
// ->to($url);
return (new SlackMessage())
->content((string)trans('email.admin_test_subject'))
->to($url);
}
/**
* Get the notification's delivery channels.
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
* @param mixed $notifiable
*
* @return array
*/
public function via($notifiable)
{
return ['slack'];
}
}

View File

@@ -49,6 +49,7 @@ use FireflyIII\Events\Security\MFANewBackupCodes;
use FireflyIII\Events\Security\MFAUsedBackupCode;
use FireflyIII\Events\StoredAccount;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Test\TestNotificationChannel;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Events\UpdatedAccount;
use FireflyIII\Events\UpdatedTransactionGroup;
@@ -135,8 +136,8 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
],
// admin related
AdminRequestedTestMessage::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestMessage',
TestNotificationChannel::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
],
NewVersionAvailable::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendNewVersion',

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Rules\Admin;
use FireflyIII\Support\Validation\ValidatesAmountsTrait;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
class IsValidDiscordUrl implements ValidationRule
{
use ValidatesAmountsTrait;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
$value = (string)$value;
if('' === $value) {
return;
}
if(!str_starts_with($value, 'https://discord.com/api/webhooks/')) {
$fail('validation.active_url')->translate();
$message = sprintf('IsValidDiscordUrl: "%s" is not a discord URL.', substr($value, 0, 255));
Log::debug($message);
Log::channel('audit')->info($message);
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace FireflyIII\Rules\Admin;
use FireflyIII\Support\Validation\ValidatesAmountsTrait;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
class IsValidSlackUrl implements ValidationRule
{
use ValidatesAmountsTrait;
/**
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function validate(string $attribute, mixed $value, \Closure $fail): void
{
$value = (string)$value;
if('' === $value) {
return;
}
if(!str_starts_with($value, 'https://hooks.slack.com/services/')) {
$fail('validation.active_url')->translate();
$message = sprintf('IsValidSlackUrl: "%s" is not a discord URL.', substr($value, 0, 255));
Log::debug($message);
Log::channel('audit')->info($message);
}
}
}

View File

@@ -54,6 +54,8 @@ use FireflyIII\Notifications\Admin\TestNotification;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\UserRegistration;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Test\TestNotificationDiscord;
use FireflyIII\Notifications\Test\TestNotificationSlack;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
@@ -399,7 +401,7 @@ class User extends Authenticatable
/**
* Route notifications for the Slack channel.
*/
public function routeNotificationForSlack(Notification $notification): string
public function routeNotificationForSlack(Notification $notification): ?string
{
// this check does not validate if the user is owner, Should be done by notification itself.
$res = app('fireflyconfig')->get('slack_webhook_url', '')->data;
@@ -407,9 +409,19 @@ class User extends Authenticatable
$res = '';
}
$res = (string)$res;
if ($notification instanceof TestNotification) {
// not the best way to do this, but alas.
if ($notification instanceof TestNotificationSlack) {
return $res;
}
if ($notification instanceof TestNotificationDiscord) {
$res = app('fireflyconfig')->get('discord_webhook_url', '')->data;
if (is_array($res)) {
$res = '';
}
return (string)$res;
}
if ($notification instanceof UserInvitation) {
return $res;
}