feat: Delete all notifications (#3529)

* Add delete all notifications option
* chore: `DELETE /api/notifications` as per conventions
* test: can delete all notifications

Co-authored-by: Sami Mazouz <ilyasmazouz@gmail.com>
This commit is contained in:
Ian Morland 2022-08-08 20:26:16 +02:00 committed by GitHub
parent b28606b8ef
commit d02bf0faa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 291 additions and 6 deletions

View File

@ -6,6 +6,7 @@ import Link from '../../common/components/Link';
import LoadingIndicator from '../../common/components/LoadingIndicator';
import Discussion from '../../common/models/Discussion';
import ItemList from '../../common/utils/ItemList';
import Tooltip from '../../common/components/Tooltip';
/**
* The `NotificationList` component displays a list of the logged-in user's
@ -34,15 +35,36 @@ export default class NotificationList extends Component {
items.add(
'mark_all_as_read',
<Button
className="Button Button--link"
icon="fas fa-check"
title={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}
onclick={state.markAllAsRead.bind(state)}
/>,
<Tooltip text={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}>
<Button
className="Button Button--link"
data-container=".NotificationList"
icon="fas fa-check"
title={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}
onclick={state.markAllAsRead.bind(state)}
/>
</Tooltip>,
70
);
items.add(
'delete_all',
<Tooltip text={app.translator.trans('core.forum.notifications.delete_all_tooltip')}>
<Button
className="Button Button--link"
data-container=".NotificationList"
icon="fas fa-trash-alt"
title={app.translator.trans('core.forum.notifications.delete_all_tooltip')}
onclick={() => {
if (confirm(app.translator.trans('core.forum.notifications.delete_all_confirm'))) {
state.deleteAll.call(state);
}
}}
/>
</Tooltip>,
50
);
return items;
}

View File

@ -46,4 +46,20 @@ export default class NotificationListState extends PaginatedListState<Notificati
method: 'POST',
});
}
/**
* Delete all of the notifications for this user.
*/
deleteAll() {
if (this.pages.length === 0) return;
app.session.user?.pushAttributes({ unreadNotificationCount: 0 });
this.pages = [];
return app.request({
url: app.forum.attribute('apiUrl') + '/notifications',
method: 'DELETE',
});
}
}

View File

@ -1,6 +1,10 @@
.NotificationList {
overflow: hidden;
.App-primaryControl > button:not(:last-of-type) {
margin-right: 4px;
}
&-header {
@media @tablet-up {
padding: 12px 15px;

View File

@ -394,6 +394,8 @@ core:
# These translations are used by the Notifications dropdown, a.k.a. "the bell".
notifications:
delete_all_confirm: Are you sure you want to delete all notifications? This action is not reversable
delete_all_tooltip: Delete all notifications
discussion_renamed_text: "{username} changed the title"
empty_text: No Notifications
mark_all_as_read_tooltip: => core.ref.mark_all_as_read

View File

@ -0,0 +1,41 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Api\Controller;
use Flarum\Http\RequestUtil;
use Flarum\Notification\Command\DeleteAllNotifications;
use Illuminate\Contracts\Bus\Dispatcher;
use Psr\Http\Message\ServerRequestInterface;
class DeleteAllNotificationsController extends AbstractDeleteController
{
/**
* @var Dispatcher
*/
protected $bus;
/**
* @param Dispatcher $bus
*/
public function __construct(Dispatcher $bus)
{
$this->bus = $bus;
}
/**
* {@inheritdoc}
*/
protected function delete(ServerRequestInterface $request)
{
$this->bus->dispatch(
new DeleteAllNotifications(RequestUtil::getActor($request))
);
}
}

View File

@ -122,6 +122,13 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
$route->toController(Controller\UpdateNotificationController::class)
);
// Delete all notifications for the current user.
$map->delete(
'/notifications',
'notifications.deleteAll',
$route->toController(Controller\DeleteAllNotificationsController::class)
);
/*
|--------------------------------------------------------------------------
| Discussions

View File

@ -0,0 +1,30 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification\Command;
use Flarum\User\User;
class DeleteAllNotifications
{
/**
* The user performing the action.
*
* @var User
*/
public $actor;
/**
* @param User $actor The user performing the action.
*/
public function __construct(User $actor)
{
$this->actor = $actor;
}
}

View File

@ -0,0 +1,54 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification\Command;
use Flarum\Notification\Event\DeletedAll;
use Flarum\Notification\NotificationRepository;
use Flarum\User\Exception\NotAuthenticatedException;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\Carbon;
class DeleteAllNotificationsHandler
{
/**
* @var NotificationRepository
*/
protected $notifications;
/**
* @var Dispatcher
*/
protected $events;
/**
* @param NotificationRepository $notifications
* @param Dispatcher $events
*/
public function __construct(NotificationRepository $notifications, Dispatcher $events)
{
$this->notifications = $notifications;
$this->events = $events;
}
/**
* @param DeleteAllNotifications $command
* @throws NotAuthenticatedException
*/
public function handle(DeleteAllNotifications $command)
{
$actor = $command->actor;
$actor->assertRegistered();
$this->notifications->deleteAll($actor);
$this->events->dispatch(new DeletedAll($actor, Carbon::now()));
}
}

View File

@ -0,0 +1,32 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Notification\Event;
use DateTime;
use Flarum\User\User;
class DeletedAll
{
/**
* @var User
*/
public $actor;
/**
* @var DateTime
*/
public $timestamp;
public function __construct(User $user, DateTime $timestamp)
{
$this->user = $user;
$this->timestamp = $timestamp;
}
}

View File

@ -54,4 +54,9 @@ class NotificationRepository
->whereNull('read_at')
->update(['read_at' => Carbon::now()]);
}
public function deleteAll(User $user)
{
Notification::where('user_id', $user->id)->delete();
}
}

View File

@ -0,0 +1,72 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Tests\integration\api\notifications;
use Carbon\Carbon;
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
use Flarum\Testing\integration\TestCase;
use Flarum\User\User;
class DeleteTest extends TestCase
{
use RetrievesAuthorizedUsers;
/**
* @inheritDoc
*/
protected function setUp(): void
{
parent::setUp();
$this->prepareDatabase([
'users' => [
$this->normalUser(),
],
'discussions' => [
['id' => 1, 'title' => 'Test Discussion', 'user_id' => 2, 'comment_count' => 1],
['id' => 2, 'title' => 'Test Discussion', 'user_id' => 2, 'comment_count' => 1],
['id' => 3, 'title' => 'Test Discussion', 'user_id' => 1, 'comment_count' => 1],
['id' => 4, 'title' => 'Test Discussion', 'user_id' => 1, 'comment_count' => 1],
],
'notifications' => [
['id' => 1, 'user_id' => 1, 'type' => 'discussionRenamed', 'subject_id' => 1, 'from_user_id' => 2, 'read_at' => Carbon::now()],
['id' => 2, 'user_id' => 1, 'type' => 'discussionRenamed', 'subject_id' => 2, 'from_user_id' => 2, 'read_at' => null],
['id' => 3, 'user_id' => 2, 'type' => 'discussionRenamed', 'subject_id' => 3, 'from_user_id' => 1, 'read_at' => Carbon::now()],
['id' => 4, 'user_id' => 2, 'type' => 'discussionRenamed', 'subject_id' => 4, 'from_user_id' => 1, 'read_at' => null],
],
]);
}
/**
* @dataProvider canDeleteAllNotifications
* @test
*/
public function user_can_delete_all_notifications(int $authenticatedAs)
{
$this->app();
$this->assertEquals(2, User::query()->find($authenticatedAs)->notifications()->count());
$response = $this->send(
$this->request('DELETE', '/api/notifications', compact('authenticatedAs')),
);
$this->assertEquals(204, $response->getStatusCode());
$this->assertEquals(0, User::query()->find($authenticatedAs)->notifications()->count());
}
public function canDeleteAllNotifications(): array
{
return [
[1],
[2]
];
}
}