Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement report notifications #783

Merged
merged 20 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
b556e4b
Implement report notifications
BentiGorlich May 15, 2024
0457c5b
Merge branch 'main' into new/report-notifications
BentiGorlich May 16, 2024
7e1f228
Add the new notifications to the API
BentiGorlich May 16, 2024
8fb4ef6
Merge branch 'main' into new/report-notifications
BentiGorlich Jun 3, 2024
22e72fa
Merge branch 'main' into new/report-notifications
BentiGorlich Jun 6, 2024
bc5e8b1
Merge branch 'main' into new/report-notifications
BentiGorlich Jun 7, 2024
bb160ae
Merge branch 'main' into new/report-notifications
BentiGorlich Jun 16, 2024
98c61a5
Merge branch 'main' into new/report-notifications
BentiGorlich Jun 30, 2024
6d4a55b
Merge branch 'main' into new/report-notifications
melroy89 Jul 9, 2024
097d455
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 11, 2024
d343d78
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 15, 2024
b03874a
Adjust recipients and API objects
BentiGorlich Jul 15, 2024
b5984f6
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 16, 2024
986ebf8
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 19, 2024
d660daa
Merge branch 'main' into new/report-notifications
Jul 19, 2024
1d9a85e
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 23, 2024
a794a4c
Merge branch 'main' into new/report-notifications
Jul 24, 2024
dbc8528
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 24, 2024
8900450
Only show "open report" for people with permission to view it
BentiGorlich Jul 24, 2024
5ec8723
Merge branch 'main' into new/report-notifications
BentiGorlich Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions migrations/Version20240515122858.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20240515122858 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add the report field for notifications';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE notification ADD report_id INT DEFAULT NULL');
$this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CA4BD2A4C0 FOREIGN KEY (report_id) REFERENCES report (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
$this->addSql('CREATE INDEX IDX_BF5476CA4BD2A4C0 ON notification (report_id)');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CA4BD2A4C0');
$this->addSql('DROP INDEX IDX_BF5476CA4BD2A4C0');
$this->addSql('ALTER TABLE notification DROP report_id');
}
}
3 changes: 3 additions & 0 deletions src/Controller/Admin/AdminReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Controller\Admin;

use App\Controller\AbstractController;
use App\Repository\NotificationRepository;
use App\Repository\ReportRepository;
use Symfony\Component\ExpressionLanguage\Expression;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -15,6 +16,7 @@ class AdminReportController extends AbstractController
{
public function __construct(
private readonly ReportRepository $repository,
private readonly NotificationRepository $notificationRepository,
) {
}

Expand All @@ -24,6 +26,7 @@ public function __invoke(Request $request, string $status): Response
$page = (int) $request->get('p', 1);

$reports = $this->repository->findAllPaginated($page, $status);
$this->notificationRepository->markReportNotificationsAsRead($this->getUserOrThrow());

return $this->render(
'admin/reports.html.twig',
Expand Down
39 changes: 39 additions & 0 deletions src/Controller/Api/Notification/NotificationBaseApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,21 @@
namespace App\Controller\Api\Notification;

use App\Controller\Api\BaseApi;
use App\DTO\EntryCommentResponseDto;
use App\DTO\EntryResponseDto;
use App\DTO\PostCommentResponseDto;
use App\DTO\PostResponseDto;
use App\Entity\Contracts\ReportInterface;
use App\Entity\Entry;
use App\Entity\EntryComment;
use App\Entity\Notification;
use App\Entity\Post;
use App\Entity\PostComment;
use App\Entity\ReportApprovedNotification;
use App\Entity\ReportCreatedNotification;
use App\Entity\ReportRejectedNotification;
use App\Factory\MessageFactory;
use GraphQL\Exception\ArgumentException;
use Symfony\Contracts\Service\Attribute\Required;
use Symfony\Contracts\Translation\TranslatorInterface;

Expand Down Expand Up @@ -110,8 +123,34 @@ protected function serializeNotification(Notification $dto)
$ban = $dto->getSubject();
$toReturn['subject'] = $this->magazineFactory->createBanDto($ban);
break;
case 'report_created_notification':
/** @var ReportCreatedNotification $n */
$n = $dto;
$toReturn['reason'] = $n->report->reason;
// no break
case 'report_rejected_notification':
case 'report_approved_notification':
/** @var ReportCreatedNotification|ReportRejectedNotification|ReportApprovedNotification $n */
$n = $dto;
$toReturn['subject'] = $this->createResponseDtoForReport($n->report->getSubject());
$toReturn['reportId'] = $n->report->getId();
break;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The notification structure has the actual subject of the notification detailed - using the report id instead breaks this convention, and would also make it impossible for regular users to have any information about the report contained in this notification displayed by the app using the API, since retrieving reports is a moderator and admin only API endpoint.
This solution would not be sufficient if we wanted to display the reason a user's post was deleted in the notification using the API, for example

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I would recommend would be to follow the convention of the other notifications and embed a subset of the report in the notification, at least the reason, magazine, type, and subjectId of the report
subjectId would be the post/comment/entry that was reported and removed

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll check that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, here are my thoughts:

  • ReportCreatedNotification: only admins and mods can get this, so imo its fine to only send the id of the report, but of course a dto of the report could be added for this.
  • ReportRejectedNotification: a user should be able to see their own reports, but if not maybe add the subject of the report here, as there is no reason supplied for rejecting a report.
  • ReportApprovedNotification: since this is going to the author of the reported content, the subject that was reported should be enough.

Would you be fine with that?

The reason I do not just want to add a report dto to all of them is simple: you should never be informed who is reporting your content for which reason, unless the report is accepted, but even then the reason the reporting user supplied is not necessarily the one the mod removed your content for. Since we do not have a "moderators_reason" field (or something similar) it would not make much sense to just relay that information to the reported user

}

return $toReturn;
}

private function createResponseDtoForReport(ReportInterface $subject): EntryCommentResponseDto|EntryResponseDto|PostCommentResponseDto|PostResponseDto
{
if ($subject instanceof Entry) {
return $this->entryFactory->createResponseDto($subject, $this->tagLinkRepository->getTagsOfEntry($subject));
} elseif ($subject instanceof EntryComment) {
return $this->entryCommentFactory->createResponseDto($subject, $this->tagLinkRepository->getTagsOfEntryComment($subject));
} elseif ($subject instanceof Post) {
return $this->postFactory->createResponseDto($subject, $this->tagLinkRepository->getTagsOfPost($subject));
} elseif ($subject instanceof PostComment) {
return $this->postCommentFactory->createResponseDto($subject, $this->tagLinkRepository->getTagsOfPostComment($subject));
}
throw new ArgumentException("cannot work with: '".\get_class($subject)."'");
}
}
3 changes: 3 additions & 0 deletions src/Controller/Magazine/Panel/MagazineReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use App\Entity\Magazine;
use App\Entity\Report;
use App\Repository\MagazineRepository;
use App\Repository\NotificationRepository;
use App\Service\ReportManager;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Component\HttpFoundation\Request;
Expand All @@ -18,6 +19,7 @@ class MagazineReportController extends AbstractController
{
public function __construct(
private readonly MagazineRepository $repository,
private readonly NotificationRepository $notificationRepository,
private readonly ReportManager $reportManager
) {
}
Expand All @@ -27,6 +29,7 @@ public function __construct(
public function reports(Magazine $magazine, Request $request, string $status): Response
{
$reports = $this->repository->findReports($magazine, $this->getPageNb($request), status: $status);
$this->notificationRepository->markReportNotificationsInMagazineAsRead($this->getUserOrThrow(), $magazine);

return $this->render(
'magazine/panel/reports.html.twig',
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/ReportController.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ private function getForm(ReportDto $dto, ReportInterface $subject): FormInterfac

private function handleReportRequest(ReportDto $dto, Request $request): Response
{
$reportError = false;
try {
$this->manager->report($dto, $this->getUserOrThrow());
$reportError = false;
$responseMessage = $this->translator->trans('subject_reported');
} catch (SubjectHasBeenReportedException $exception) {
$reportError = true;
Expand Down
3 changes: 3 additions & 0 deletions src/Controller/User/Profile/UserReportsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Controller\AbstractController;
use App\Repository\MagazineRepository;
use App\Repository\NotificationRepository;
use App\Repository\ReportRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
Expand All @@ -17,6 +18,7 @@ class UserReportsController extends AbstractController

public function __construct(
private readonly ReportRepository $repository,
private readonly NotificationRepository $notificationRepository,
) {
}

Expand All @@ -25,6 +27,7 @@ public function __invoke(MagazineRepository $repository, Request $request, strin
{
$user = $this->getUserOrThrow();
$reports = $this->repository->findByUserPaginated($user, $this->getPageNb($request), status: $status);
$this->notificationRepository->markOwnReportNotificationsAsRead($this->getUserOrThrow());

return $this->render(
'user/settings/reports.html.twig',
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@
'post_comment_mentioned' => 'PostCommentMentionedNotification',
'message' => 'MessageNotification',
'ban' => 'MagazineBanNotification',
'report_created' => 'ReportCreatedNotification',
'report_approved' => 'ReportApprovedNotification',
'report_rejected' => 'ReportRejectedNotification',
])]
abstract class Notification
{
Expand Down
29 changes: 29 additions & 0 deletions src/Entity/ReportApprovedNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;

#[Entity]
class ReportApprovedNotification extends Notification
{
#[ManyToOne(targetEntity: Report::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?Report $report = null;

public function __construct(User $receiver, Report $report)
{
parent::__construct($receiver);

$this->report = $report;
}

public function getType(): string
{
return 'report_approved_notification';
}
}
29 changes: 29 additions & 0 deletions src/Entity/ReportCreatedNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;

#[Entity]
class ReportCreatedNotification extends Notification
{
#[ManyToOne(targetEntity: Report::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?Report $report = null;

public function __construct(User $receiver, Report $report)
{
parent::__construct($receiver);

$this->report = $report;
}

public function getType(): string
{
return 'report_created_notification';
}
}
29 changes: 29 additions & 0 deletions src/Entity/ReportRejectedNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;

#[Entity]
class ReportRejectedNotification extends Notification
{
#[ManyToOne(targetEntity: Report::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?Report $report = null;

public function __construct(User $receiver, Report $report)
{
parent::__construct($receiver);

$this->report = $report;
}

public function getType(): string
{
return 'report_rejected_notification';
}
}
29 changes: 29 additions & 0 deletions src/EventSubscriber/ReportApprovedSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use App\Event\Report\ReportApprovedEvent;
use App\Service\Notification\ReportNotificationManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ReportApprovedSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly ReportNotificationManager $notificationManager,
) {
}

public function onReportApproved(ReportApprovedEvent $reportedEvent): void
{
$this->notificationManager->sendReportApprovedNotification($reportedEvent->report);
}

public static function getSubscribedEvents(): array
{
return [
ReportApprovedEvent::class => 'onReportApproved',
];
}
}
29 changes: 29 additions & 0 deletions src/EventSubscriber/ReportRejectedSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace App\EventSubscriber;

use App\Event\Report\ReportRejectedEvent;
use App\Service\Notification\ReportNotificationManager;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class ReportRejectedSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly ReportNotificationManager $notificationManager,
) {
}

public function onReportRejected(ReportRejectedEvent $reportedEvent): void
{
$this->notificationManager->sendReportRejectedNotification($reportedEvent->report);
}

public static function getSubscribedEvents(): array
{
return [
ReportRejectedEvent::class => 'onReportRejected',
];
}
}
3 changes: 3 additions & 0 deletions src/EventSubscriber/SubjectReportedSubscriber.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use App\Event\Report\SubjectReportedEvent;
use App\Message\ActivityPub\Outbox\FlagMessage;
use App\Service\Notification\ReportNotificationManager;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface;
Expand All @@ -15,12 +16,14 @@ class SubjectReportedSubscriber implements EventSubscriberInterface
public function __construct(
private readonly MessageBusInterface $bus,
private readonly LoggerInterface $logger,
private readonly ReportNotificationManager $notificationManager,
) {
}

public function onSubjectReported(SubjectReportedEvent $reportedEvent): void
{
$this->logger->debug($reportedEvent->report->reported->username.' was reported for '.$reportedEvent->report->reason);
$this->notificationManager->sendReportCreatedNotification($reportedEvent->report);
if (!$reportedEvent->report->magazine->apId and 'random' !== $reportedEvent->report->magazine->name) {
return;
}
Expand Down
Loading
Loading