Make news comments deletable

This commit is contained in:
Igor Scheller 2021-07-14 01:56:03 +02:00 committed by msquare
parent 950a865c0c
commit a8e012be72
7 changed files with 145 additions and 45 deletions

View File

@ -39,6 +39,7 @@ $route->get('/news', 'NewsController@index');
$route->get('/meetings', 'NewsController@meetings'); $route->get('/meetings', 'NewsController@meetings');
$route->get('/news/{id:\d+}', 'NewsController@show'); $route->get('/news/{id:\d+}', 'NewsController@show');
$route->post('/news/{id:\d+}', 'NewsController@comment'); $route->post('/news/{id:\d+}', 'NewsController@comment');
$route->post('/news/comment/{id:\d+}', 'NewsController@deleteComment');
// FAQ // FAQ
$route->get('/faq', 'FaqController@index'); $route->get('/faq', 'FaqController@index');

View File

@ -74,6 +74,9 @@ msgstr "Die Minuten nach dem Talk müssen eine Zahl sein."
msgid "news.comment.success" msgid "news.comment.success"
msgstr "Kommentar gespeichert." msgstr "Kommentar gespeichert."
msgid "news.comment-delete.success"
msgstr "Kommentar erfolgreich gelöscht."
msgid "news.edit.success" msgid "news.edit.success"
msgstr "News erfolgreich aktualisiert." msgstr "News erfolgreich aktualisiert."

View File

@ -72,6 +72,9 @@ msgstr "The minutes after the talk have to be an integer."
msgid "news.comment.success" msgid "news.comment.success"
msgstr "Comment saved." msgstr "Comment saved."
msgid "news.comment-delete.success"
msgstr "Comment successfully deleted."
msgid "news.edit.success" msgid "news.edit.success"
msgstr "News successfully updated." msgstr "News successfully updated."

View File

@ -22,6 +22,17 @@
{{ comment.created_at.format(__('Y-m-d H:i')) }} {{ comment.created_at.format(__('Y-m-d H:i')) }}
{{ m.user(comment.user) }} {{ m.user(comment.user) }}
{% if comment.user.id == user.id or has_permission_to('admin_news') or has_permission_to('comment.delete') %}
<div class="pull-right">
<form
action="{{ url('/news/comment/' ~ comment.id) }}" enctype="multipart/form-data"
method="post">
{{ csrf() }}
{{ f.submit(m.glyphicon('trash'), {'name': 'delete', 'btn_type': 'danger', 'btn_size': 'xs', 'title': __('form.delete')}) }}
</form>
</div>
{% endif %}
</div> </div>
</div> </div>
{% endfor %} {% endfor %}

View File

@ -4,6 +4,7 @@ namespace Engelsystem\Controllers;
use Engelsystem\Config\Config; use Engelsystem\Config\Config;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
@ -18,6 +19,9 @@ class NewsController extends BaseController
/** @var Authenticator */ /** @var Authenticator */
protected $auth; protected $auth;
/** @var NewsComment */
protected $comment;
/** @var Config */ /** @var Config */
protected $config; protected $config;
@ -41,11 +45,13 @@ class NewsController extends BaseController
'news', 'news',
'meetings' => 'user_meetings', 'meetings' => 'user_meetings',
'comment' => 'news_comments', 'comment' => 'news_comments',
'deleteComment' => 'news_comments',
]; ];
/** /**
* @param Authenticator $auth * @param Authenticator $auth
* @param Config $config * @param Config $config
* @param NewsComment $comment
* @param LoggerInterface $log * @param LoggerInterface $log
* @param News $news * @param News $news
* @param Redirector $redirector * @param Redirector $redirector
@ -54,6 +60,7 @@ class NewsController extends BaseController
*/ */
public function __construct( public function __construct(
Authenticator $auth, Authenticator $auth,
NewsComment $comment,
Config $config, Config $config,
LoggerInterface $log, LoggerInterface $log,
News $news, News $news,
@ -62,6 +69,7 @@ class NewsController extends BaseController
Request $request Request $request
) { ) {
$this->auth = $auth; $this->auth = $auth;
$this->comment = $comment;
$this->config = $config; $this->config = $config;
$this->log = $log; $this->log = $log;
$this->news = $news; $this->news = $news;
@ -132,6 +140,41 @@ class NewsController extends BaseController
return $this->redirect->back(); return $this->redirect->back();
} }
/**
* @param Request $request
*
* @return Response
*/
public function deleteComment(Request $request): Response
{
$id = $request->getAttribute('id');
$this->validate(
$request,
[
'delete' => 'checked',
]
);
$comment = $this->comment->findOrFail($id);
if (
$comment->user->id != $this->auth->user()->id
&& !$this->auth->can('admin_news')
&& !$this->auth->can('comment.delete')
) {
throw new HttpForbidden();
}
$comment->delete();
$this->log->info(
'Deleted comment "{comment}" of news "{news}"',
['comment' => $comment->text, 'news' => $comment->news->title]
);
$this->addNotification('news.comment-delete.success');
return $this->redirect->to('/news/' . $comment->news->id);
}
/** /**
* @param bool $onlyMeetings * @param bool $onlyMeetings
* @return Response * @return Response

View File

@ -72,5 +72,6 @@ abstract class ControllerTest extends TestCase
$this->config = new Config(); $this->config = new Config();
$this->app->instance('config', $this->config); $this->app->instance('config', $this->config);
$this->app->instance(Config::class, $this->config);
} }
} }

View File

@ -2,30 +2,20 @@
namespace Engelsystem\Test\Unit\Controllers; namespace Engelsystem\Test\Unit\Controllers;
use Engelsystem\Config\Config;
use Engelsystem\Controllers\NewsController; use Engelsystem\Controllers\NewsController;
use Engelsystem\Helpers\Authenticator; use Engelsystem\Helpers\Authenticator;
use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Request;
use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator;
use Engelsystem\Http\UrlGeneratorInterface;
use Engelsystem\Http\Validation\Validator; use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\News; use Engelsystem\Models\News;
use Engelsystem\Models\NewsComment; use Engelsystem\Models\NewsComment;
use Engelsystem\Models\User\User; use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\HasDatabase; use Engelsystem\Test\Unit\HasDatabase;
use Engelsystem\Test\Unit\TestCase;
use Illuminate\Database\Eloquent\ModelNotFoundException; use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\Test\TestLogger;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\Storage\MockArraySessionStorage;
class NewsControllerTest extends TestCase class NewsControllerTest extends ControllerTest
{ {
use HasDatabase; use HasDatabase;
@ -75,15 +65,6 @@ class NewsControllerTest extends TestCase
], ],
]; ];
/** @var TestLogger */
protected $log;
/** @var Response|MockObject */
protected $response;
/** @var Request */
protected $request;
/** /**
* @covers \Engelsystem\Controllers\NewsController::__construct * @covers \Engelsystem\Controllers\NewsController::__construct
* @covers \Engelsystem\Controllers\NewsController::index * @covers \Engelsystem\Controllers\NewsController::index
@ -225,51 +206,108 @@ class NewsControllerTest extends TestCase
$this->log->hasInfoThatContains('Created news comment'); $this->log->hasInfoThatContains('Created news comment');
/** @var NewsComment $comment */ /** @var NewsComment $comment */
$comment = NewsComment::whereNewsId(1)->first(); $comment = NewsComment::whereNewsId(1)->get()[2];
$this->assertEquals('Foo bar!', $comment->text); $this->assertEquals('Foo bar!', $comment->text);
} }
/**
* @covers \Engelsystem\Controllers\NewsController::deleteComment
*/
public function testDeleteCommentInvalidRequest()
{
/** @var NewsController $controller */
$controller = $this->app->get(NewsController::class);
$controller->setValidator($this->app->get(Validator::class));
$this->expectException(ValidationException::class);
$controller->deleteComment($this->request);
}
/**
* @covers \Engelsystem\Controllers\NewsController::deleteComment
*/
public function testDeleteCommentNotFound()
{
$this->request = $this->request->withAttribute('id', 42)->withParsedBody(['delete' => '1']);
/** @var NewsController $controller */
$controller = $this->app->get(NewsController::class);
$controller->setValidator($this->app->get(Validator::class));
$this->expectException(ModelNotFoundException::class);
$controller->deleteComment($this->request);
}
/**
* @covers \Engelsystem\Controllers\NewsController::deleteComment
*/
public function testDeleteCommentNotAllowed()
{
$this->request = $this->request->withAttribute('id', 2)->withParsedBody(['delete' => '1']);
$this->addUser(1);
$this->addUser(2);
/** @var NewsController $controller */
$controller = $this->app->get(NewsController::class);
$controller->setValidator($this->app->get(Validator::class));
$this->expectException(HttpForbidden::class);
$controller->deleteComment($this->request);
}
/**
* @covers \Engelsystem\Controllers\NewsController::deleteComment
*/
public function testDeleteComment()
{
$this->request = $this->request->withAttribute('id', 1)->withParsedBody(['delete' => '1']);
$this->setExpects($this->response, 'redirectTo', ['http://localhost/news/1'], $this->response);
$this->addUser(1);
/** @var NewsController $controller */
$controller = $this->app->get(NewsController::class);
$controller->setValidator($this->app->get(Validator::class));
$controller->deleteComment($this->request);
$this->assertCount(1, NewsComment::all());
$this->assertTrue($this->log->hasInfoThatContains('Deleted comment'));
$this->assertHasNotification('news.comment-delete.success');
}
/** /**
* Setup environment * Setup environment
*/ */
public function setUp(): void public function setUp(): void
{ {
parent::setUp(); parent::setUp();
$this->initDatabase();
$this->request = new Request(); $this->config->set(['display_news' => 2]);
$this->app->instance('request', $this->request);
$this->app->instance(Request::class, $this->request);
$this->app->instance(ServerRequestInterface::class, $this->request);
$this->response = $this->createMock(Response::class);
$this->app->instance(Response::class, $this->response);
$this->app->instance(Config::class, new Config(['display_news' => 2]));
$this->log = new TestLogger();
$this->app->instance(LoggerInterface::class, $this->log);
$this->app->instance('session', new Session(new MockArraySessionStorage()));
$this->auth = $this->createMock(Authenticator::class); $this->auth = $this->createMock(Authenticator::class);
$this->app->instance(Authenticator::class, $this->auth); $this->app->instance(Authenticator::class, $this->auth);
$this->app->bind(UrlGeneratorInterface::class, UrlGenerator::class);
$this->app->instance('config', new Config());
foreach ($this->data as $news) { foreach ($this->data as $news) {
(new News($news))->save(); (new News($news))->save();
} }
foreach ([1, 2] as $i) {
NewsComment::create([
'news_id' => 1,
'text' => 'test comment ' . $i,
'user_id' => $i,
]);
}
} }
/** /**
* Creates a new user * Creates a new user
*/ */
protected function addUser() protected function addUser(int $id = 42)
{ {
$user = User::factory()->create(['id' => 42]); $user = User::factory()->create(['id' => $id]);
$this->auth->expects($this->any()) $this->auth->expects($this->any())
->method('user') ->method('user')