mirror of
https://github.com/flarum/core.git
synced 2025-07-23 09:41:26 +02:00
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:
@@ -6,6 +6,7 @@ import Link from '../../common/components/Link';
|
|||||||
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
import LoadingIndicator from '../../common/components/LoadingIndicator';
|
||||||
import Discussion from '../../common/models/Discussion';
|
import Discussion from '../../common/models/Discussion';
|
||||||
import ItemList from '../../common/utils/ItemList';
|
import ItemList from '../../common/utils/ItemList';
|
||||||
|
import Tooltip from '../../common/components/Tooltip';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `NotificationList` component displays a list of the logged-in user's
|
* The `NotificationList` component displays a list of the logged-in user's
|
||||||
@@ -34,15 +35,36 @@ export default class NotificationList extends Component {
|
|||||||
|
|
||||||
items.add(
|
items.add(
|
||||||
'mark_all_as_read',
|
'mark_all_as_read',
|
||||||
<Button
|
<Tooltip text={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}>
|
||||||
className="Button Button--link"
|
<Button
|
||||||
icon="fas fa-check"
|
className="Button Button--link"
|
||||||
title={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}
|
data-container=".NotificationList"
|
||||||
onclick={state.markAllAsRead.bind(state)}
|
icon="fas fa-check"
|
||||||
/>,
|
title={app.translator.trans('core.forum.notifications.mark_all_as_read_tooltip')}
|
||||||
|
onclick={state.markAllAsRead.bind(state)}
|
||||||
|
/>
|
||||||
|
</Tooltip>,
|
||||||
70
|
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;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -46,4 +46,20 @@ export default class NotificationListState extends PaginatedListState<Notificati
|
|||||||
method: 'POST',
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,10 @@
|
|||||||
.NotificationList {
|
.NotificationList {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.App-primaryControl > button:not(:last-of-type) {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&-header {
|
&-header {
|
||||||
@media @tablet-up {
|
@media @tablet-up {
|
||||||
padding: 12px 15px;
|
padding: 12px 15px;
|
||||||
|
@@ -394,6 +394,8 @@ core:
|
|||||||
|
|
||||||
# These translations are used by the Notifications dropdown, a.k.a. "the bell".
|
# These translations are used by the Notifications dropdown, a.k.a. "the bell".
|
||||||
notifications:
|
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"
|
discussion_renamed_text: "{username} changed the title"
|
||||||
empty_text: No Notifications
|
empty_text: No Notifications
|
||||||
mark_all_as_read_tooltip: => core.ref.mark_all_as_read
|
mark_all_as_read_tooltip: => core.ref.mark_all_as_read
|
||||||
|
@@ -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))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@@ -122,6 +122,13 @@ return function (RouteCollection $map, RouteHandlerFactory $route) {
|
|||||||
$route->toController(Controller\UpdateNotificationController::class)
|
$route->toController(Controller\UpdateNotificationController::class)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Delete all notifications for the current user.
|
||||||
|
$map->delete(
|
||||||
|
'/notifications',
|
||||||
|
'notifications.deleteAll',
|
||||||
|
$route->toController(Controller\DeleteAllNotificationsController::class)
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Discussions
|
| Discussions
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
@@ -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()));
|
||||||
|
}
|
||||||
|
}
|
32
framework/core/src/Notification/Event/DeletedAll.php
Normal file
32
framework/core/src/Notification/Event/DeletedAll.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -54,4 +54,9 @@ class NotificationRepository
|
|||||||
->whereNull('read_at')
|
->whereNull('read_at')
|
||||||
->update(['read_at' => Carbon::now()]);
|
->update(['read_at' => Carbon::now()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deleteAll(User $user)
|
||||||
|
{
|
||||||
|
Notification::where('user_id', $user->id)->delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user