Skip to content

Commit

Permalink
feat(notification): web push notification support
Browse files Browse the repository at this point in the history
  • Loading branch information
bernard-ng committed Jul 1, 2022
1 parent c19415e commit 1eb79e1
Show file tree
Hide file tree
Showing 17 changed files with 95 additions and 88 deletions.
1 change: 1 addition & 0 deletions config/domains/notification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ services:

# Doctrine Repositories
Domain\Notification\Repository\NotificationRepositoryInterface: '@Infrastructure\Notification\Doctrine\Repository\NotificationRepository'
Domain\Notification\Repository\PushSubscriptionRepositoryInterface: '@Infrastructure\Notification\Doctrine\Repository\PushSubscriptionRepository'
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ final class SetAllNotificationsReadCommand
{
public function __construct(
public readonly User $user
)
{
) {
}
}
14 changes: 9 additions & 5 deletions src/Application/Notification/Service/NotificationService.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ private function getHashForEntity(object $entity): string
{
$hash = $entity::class;
if (method_exists($entity, 'getId')) {
$hash .= sprintf('::%s', (string)$entity->getId());
$hash .= sprintf('::%s', (string) $entity->getId());
}

return $hash;
}

private function getUrlForEntityChannel(object $entity): ?string
private function getUrlForEntityChannel(object $entity): string
{
return null;
return '';
}

private function getUrlForEntityUser(object $entity, User $user): string
Expand All @@ -132,8 +132,12 @@ private function getUrlForEntityUser(object $entity, User $user): string
'report_employee_report_show' :
'report_manager_report_show';
$parameters = match (true) {
$entity instanceof Evaluation => ['uuid' => $entity->getReport()->getUuid()],
$entity instanceof Report => ['uuid' => $entity->getUuid()],
$entity instanceof Evaluation => [
'uuid' => $entity->getReport()?->getUuid(),
],
$entity instanceof Report => [
'uuid' => $entity->getUuid(),
],
default => []
};

Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Notification/Entity/Notification.php
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public function getIsRead(): bool
return $this->is_read;
}

public function setIsRead(?bool $is_read): self
public function setIsRead(bool $is_read): self
{
$this->is_read = $is_read;

Expand Down
1 change: 1 addition & 0 deletions src/Domain/Notification/Entity/PushSubscription.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public function getUser(): ?User
public function setUser(?User $user): self
{
$this->user = $user;

return $this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ public function findRecentForUser(User $user, array $channels = ['public']): arr

public function countUnreadForUser(User $user): int;

public function setAllReadForUser(User $user);
public function setAllReadForUser(User $user): int;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,5 @@
*/
interface PushSubscriptionRepositoryInterface extends DataRepositoryInterface
{
public function deleteSubscriptionByEndpoint(string $endpoint): int;
}
4 changes: 2 additions & 2 deletions src/Domain/Notification/ValueObject/PushSubscriptionKeys.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class PushSubscriptionKeys
public readonly string $p256dh;
public readonly string $auth;

private function __construct($p256dh, $auth)
private function __construct(string $p256dh, string $auth)
{
Assert::notEmpty($p256dh);
Assert::notEmpty($auth);
Expand All @@ -37,7 +37,7 @@ public function toArray(): array
{
return [
'auth' => $this->auth,
'p256dh' => $this->p256dh
'p256dh' => $this->p256dh,
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,35 @@ public function __construct(ManagerRegistry $registry)
parent::__construct($registry, PushSubscription::class);
}

/**
* @param PushSubscription $entity
* @return void
*/
public function save(object $entity): void
{
$subscription = $this->findOneBy(['endpoint' => $entity->getEndpoint()]);

if ($subscription) {
$subscription
->setExpirationTime($entity->getExpirationTime())
->setKeys($entity->getKeys())
->setUpdatedAt($entity->getCreatedAt());
} else {
$subscription = $entity;
if ($entity instanceof PushSubscription) {
/** @var PushSubscription|null $subscription */
$subscription = $this->findOneBy([
'endpoint' => $entity->getEndpoint(),
]);

if ($subscription) {
$subscription
->setExpirationTime($entity->getExpirationTime())
->setKeys($entity->getKeys())
->setUpdatedAt($entity->getCreatedAt());
} else {
$subscription = $entity;
}

$this->getEntityManager()->persist($subscription);
$this->getEntityManager()->flush();
}
}

$this->getEntityManager()->persist($subscription);
$this->getEntityManager()->flush();
public function deleteSubscriptionByEndpoint(string $endpoint): int
{
return intval($this->createQueryBuilder('ps')
->delete(PushSubscription::class, 'ps')
->where('ps.endpoint = :endpoint')
->setParameter('endpoint', $endpoint)
->getQuery()
->execute());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
use Domain\Authentication\Entity\User;
use Domain\Notification\Event\NotificationCreatedEvent;
use Domain\Notification\Event\NotificationReadEvent;
use Infrastructure\Notification\WebPushService;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update;
Expand All @@ -21,7 +20,6 @@ final class MercureEventSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly HubInterface $hub,
private readonly WebPushService $push
) {
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ public function index(
target: $repository->findRecentForUser($user),
page: $request->query->getInt('page', 1),
limit: 20
)
),
]
);
}
Expand All @@ -60,7 +60,7 @@ public function show(Notification $notification): Response
$this->handleUnexpectedException($e);
}

return $this->redirect($notification->getUrl(), status: Response::HTTP_PERMANENTLY_REDIRECT);
return $this->redirect((string) $notification->getUrl(), status: Response::HTTP_PERMANENTLY_REDIRECT);
}

#[Route('/set_as_read', name: 'read', methods: ['POST'])]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
namespace Infrastructure\Notification\Symfony\Controller;

use Domain\Authentication\Entity\User;
use Domain\Notification\Entity\Notification;
use Domain\Notification\Entity\PushSubscription;
use Domain\Notification\Repository\PushSubscriptionRepositoryInterface;
use Infrastructure\Notification\WebPushService;
use Infrastructure\Shared\Symfony\Controller\AbstractController;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\HttpFoundation\JsonResponse;
Expand All @@ -30,28 +28,19 @@ public function subscribe(Request $request, PushSubscriptionRepositoryInterface
/** @var User $user */
$user = $this->getUser();
$subscription = PushSubscription::fromArray(
data: json_decode($request->getContent(), associative: true),
data: (array) json_decode($request->getContent(), associative: true),
user: $user
);
$repository->save($subscription);

return new JsonResponse(status: Response::HTTP_CREATED);
}

#[Route('/notification/push/key', name: 'notification_push_key', methods: ['GET'])]
public function key(): Response
{
return new JsonResponse(['key' => $_ENV['VAPID_PUBLIC_KEY']]);
}

#[Route('notification/ping')]
public function ping(WebPushService $service): Response
{
$notification = (new Notification())
->setMessage("Hello world")
->setUrl("hello");

$service->notifyChannel($notification);

return $this->redirectSeeOther('notification_index');
return new JsonResponse([
'key' => $_ENV['VAPID_PUBLIC_KEY'],
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@ final class NotificationExtension extends AbstractExtension
{
public function __construct(
private readonly NotificationService $notificationService,
private readonly Security $security
)
{
private readonly Security $security
) {
}

public function getFunctions(): array
{
return [
new TwigFunction('recent_notifications', [$this, 'notifications']),
new TwigFunction('count_notifications', [$this, 'count'])
new TwigFunction('count_notifications', [$this, 'count']),
];
}

Expand All @@ -48,6 +47,7 @@ public function count(): int
{
/** @var User $user */
$user = $this->security->getUser();

return $this->notificationService->countForUser($user);
}
}
46 changes: 24 additions & 22 deletions src/Infrastructure/Notification/WebPushService.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use Domain\Authentication\Entity\User;
use Domain\Notification\Entity\Notification;
use Domain\Notification\Entity\PushSubscription;
use Domain\Notification\Repository\PushSubscriptionRepositoryInterface;
use Infrastructure\Notification\Doctrine\Repository\PushSubscriptionRepository;
use Minishlink\WebPush\Subscription;
use Minishlink\WebPush\WebPush;
use Psr\Log\LoggerInterface;
Expand All @@ -24,23 +24,23 @@ final class WebPushService
private ?string $icon;

public function __construct(
private readonly PushSubscriptionRepositoryInterface $repository,
private readonly PushSubscriptionRepository $repository,
private readonly LoggerInterface $logger,
RequestStack $stack
) {
$this->webPush = new WebPush([
'VAPID' => [
'subject' => 'mailto:[email protected]',
'publicKey' => $_ENV['VAPID_PUBLIC_KEY'],
'privateKey' => $_ENV['VAPID_PRIVATE_KEY']
]
'privateKey' => $_ENV['VAPID_PRIVATE_KEY'],
],
]);
$this->icon = $stack->getCurrentRequest()?->getUriForPath('/images/logo_icon.png');
}

public function notifyChannel(Notification $notification): void
{
/** @var PushSubscription[] $subscription */
/** @var PushSubscription[] $subscriptions */
$subscriptions = $this->repository->findAll();

try {
Expand All @@ -49,11 +49,8 @@ public function notifyChannel(Notification $notification): void
}

foreach ($this->webPush->flush() as $report) {
if (!$report->isSuccess()) {
$this->repository->delete($subscription);
dd($report);
} else {
dd($report);
if (! $report->isSuccess()) {
$this->repository->deleteSubscriptionByEndpoint($report->getEndpoint());
}
}
} catch (\Throwable $e) {
Expand All @@ -64,17 +61,19 @@ public function notifyChannel(Notification $notification): void

public function notifyUser(Notification $notification, User $user): void
{
/** @var PushSubscription[] $subscription */
$subscriptions = $this->repository->findBy(['user' => $user]);
/** @var PushSubscription[] $subscriptions */
$subscriptions = $this->repository->findBy([
'user' => $user,
]);

try {
foreach ($subscriptions as $subscription) {
$this->push($subscription, $notification);
}

foreach ($this->webPush->flush() as $report) {
if (!$report->isSuccess()) {
$this->repository->delete($subscription);
if (! $report->isSuccess()) {
$this->repository->deleteSubscriptionByEndpoint($report->getEndpoint());
}
}
} catch (\Throwable $e) {
Expand All @@ -90,25 +89,28 @@ private function push(PushSubscription $subscription, Notification $notification
$this->webPush->queueNotification(
subscription: Subscription::create([
'endpoint' => $subscription->getEndpoint(),
'publicKey' => $subscription->getKeys()->p256dh,
'authToken' => $subscription->getKeys()->auth
'publicKey' => $subscription->getKeys()?->p256dh,
'authToken' => $subscription->getKeys()?->auth,
]),
payload: json_encode([
'title' => 'UNH Rapport',
'options' => [
'body' => $notification->getMessage(),
'data' => [
"url" => $notification->getUrl(),
'url' => $notification->getUrl(),
],
'actions' => [
["action" => "show", "title" => "Voir les détails"]
[
'action' => 'show',
'title' => 'Voir les détails',
],
],
"icon" => $this->icon,
'icon' => $this->icon,
'requireInteraction' => true,
'timestamp' => $notification->getCreatedAt()->format('u'),
'lang' => 'FR'
'timestamp' => $notification->getCreatedAt()?->format('u'),
'lang' => 'FR',
],
])
]) ?: null
);
}
}
Loading

0 comments on commit 1eb79e1

Please sign in to comment.