mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-07-03 15:36:14 +00:00
notification: store users which are unread instead of read, and in
dedicated table The query "which are the unread notification" is much more frequent than the read one. We then store the unread items in a dedicated table.
This commit is contained in:
parent
f6f0786d38
commit
bd3919efcb
@ -11,7 +11,6 @@
|
||||
"php": "^7.4",
|
||||
"champs-libres/async-uploader-bundle": "dev-sf4#d57134aee8e504a83c902ff0cf9f8d36ac418290",
|
||||
"champs-libres/wopi-bundle": "dev-master#59b468503b9413f8d588ef9e626e7675560db3d8",
|
||||
"ocramius/package-versions": "^1.10",
|
||||
"doctrine/doctrine-bundle": "^2.1",
|
||||
"doctrine/doctrine-migrations-bundle": "^3.0",
|
||||
"doctrine/orm": "^2.7",
|
||||
@ -22,6 +21,7 @@
|
||||
"knplabs/knp-time-bundle": "^1.12",
|
||||
"league/csv": "^9.7.1",
|
||||
"nyholm/psr7": "^1.4",
|
||||
"ocramius/package-versions": "^1.10",
|
||||
"phpoffice/phpspreadsheet": "^1.16",
|
||||
"ramsey/uuid-doctrine": "^1.7",
|
||||
"sensio/framework-extra-bundle": "^5.5",
|
||||
@ -47,6 +47,7 @@
|
||||
"twig/extra-bundle": "^3.0",
|
||||
"twig/intl-extra": "^3.0",
|
||||
"twig/markdown-extra": "^3.3",
|
||||
"twig/string-extra": "^3.3",
|
||||
"twig/twig": "^3.0"
|
||||
},
|
||||
"conflict": {
|
||||
@ -72,7 +73,13 @@
|
||||
"bin-dir": "bin",
|
||||
"optimize-autoloader": true,
|
||||
"sort-packages": true,
|
||||
"vendor-dir": "tests/app/vendor"
|
||||
"vendor-dir": "tests/app/vendor",
|
||||
"allow-plugins": {
|
||||
"composer/package-versions-deprecated": true,
|
||||
"phpstan/extension-installer": true,
|
||||
"ergebnis/composer-normalize": true,
|
||||
"phpro/grumphp": true
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
@ -118,9 +118,9 @@ class NotificationController extends AbstractController
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/my", name="chill_main_notification_my")
|
||||
* @Route("/inbox", name="chill_main_notification_my")
|
||||
*/
|
||||
public function myAction(): Response
|
||||
public function inboxAction(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
$currentUser = $this->security->getUser();
|
||||
@ -134,21 +134,61 @@ class NotificationController extends AbstractController
|
||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render('@ChillMain/Notification/list.html.twig', [
|
||||
'datas' => $this->itemsForTemplate($notifications),
|
||||
'notifications' => $notifications,
|
||||
'paginator' => $paginator,
|
||||
'step' => 'inbox',
|
||||
'unreads' => $this->countUnread(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route("/sent", name="chill_main_notification_sent")
|
||||
*/
|
||||
public function sentAction(): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_REMEMBERED');
|
||||
$currentUser = $this->security->getUser();
|
||||
|
||||
$notificationsNbr = $this->notificationRepository->countAllForSender($currentUser);
|
||||
$paginator = $this->paginatorFactory->create($notificationsNbr);
|
||||
|
||||
$notifications = $this->notificationRepository->findAllForSender(
|
||||
$currentUser,
|
||||
$limit = $paginator->getItemsPerPage(),
|
||||
$offset = $paginator->getCurrentPage()->getFirstItemNumber()
|
||||
);
|
||||
|
||||
return $this->render('@ChillMain/Notification/list.html.twig', [
|
||||
'datas' => $this->itemsForTemplate($notifications),
|
||||
'notifications' => $notifications,
|
||||
'paginator' => $paginator,
|
||||
'step' => 'sent',
|
||||
'unreads' => $this->countUnread(),
|
||||
]);
|
||||
}
|
||||
|
||||
private function countUnread(): array
|
||||
{
|
||||
return [
|
||||
'sent' => $this->notificationRepository->countUnreadByUserWhereSender($this->security->getUser()),
|
||||
'inbox' => $this->notificationRepository->countUnreadByUserWhereAddressee($this->security->getUser()),
|
||||
];
|
||||
}
|
||||
|
||||
private function itemsForTemplate(array $notifications): array
|
||||
{
|
||||
$templateData = [];
|
||||
|
||||
foreach ($notifications as $notification) {
|
||||
$data = [
|
||||
$templateData[] = [
|
||||
'template' => $this->notificationHandlerManager->getTemplate($notification),
|
||||
'template_data' => $this->notificationHandlerManager->getTemplateData($notification),
|
||||
'notification' => $notification,
|
||||
];
|
||||
$templateData[] = $data;
|
||||
}
|
||||
|
||||
return $this->render('@ChillMain/Notification/show.html.twig', [
|
||||
'datas' => $templateData,
|
||||
'notifications' => $notifications,
|
||||
'paginator' => $paginator,
|
||||
]);
|
||||
return $templateData;
|
||||
}
|
||||
}
|
||||
|
@ -50,11 +50,6 @@ class Notification
|
||||
*/
|
||||
private string $message = '';
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="json")
|
||||
*/
|
||||
private array $read = [];
|
||||
|
||||
/**
|
||||
* @ORM\Column(type="string", length=255)
|
||||
*/
|
||||
@ -71,9 +66,16 @@ class Notification
|
||||
*/
|
||||
private User $sender;
|
||||
|
||||
/**
|
||||
* @ORM\ManyToMany(targetEntity=User::class)
|
||||
* @ORM\JoinTable(name="chill_main_notification_addresses_unread")
|
||||
*/
|
||||
private Collection $unreadBy;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->addressees = new ArrayCollection();
|
||||
$this->unreadBy = new ArrayCollection();
|
||||
$this->setDate(new DateTimeImmutable());
|
||||
}
|
||||
|
||||
@ -81,6 +83,16 @@ class Notification
|
||||
{
|
||||
if (!$this->addressees->contains($addressee)) {
|
||||
$this->addressees[] = $addressee;
|
||||
$this->addUnreadBy($addressee);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addUnreadBy(User $user): self
|
||||
{
|
||||
if (!$this->unreadBy->contains($user)) {
|
||||
$this->unreadBy->add($user);
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -109,11 +121,6 @@ class Notification
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function getRead(): array
|
||||
{
|
||||
return $this->read;
|
||||
}
|
||||
|
||||
public function getRelatedEntityClass(): ?string
|
||||
{
|
||||
return $this->relatedEntityClass;
|
||||
@ -129,9 +136,32 @@ class Notification
|
||||
return $this->sender;
|
||||
}
|
||||
|
||||
public function isReadBy(User $user): bool
|
||||
{
|
||||
return !$this->unreadBy->contains($user);
|
||||
}
|
||||
|
||||
public function markAsReadBy(User $user): self
|
||||
{
|
||||
return $this->removeUnreadBy($user);
|
||||
}
|
||||
|
||||
public function markAsUnreadBy(User $user): self
|
||||
{
|
||||
return $this->addUnreadBy($user);
|
||||
}
|
||||
|
||||
public function removeAddressee(User $addressee): self
|
||||
{
|
||||
$this->addressees->removeElement($addressee);
|
||||
$this->unreadBy->removeElement($addressee);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function removeUnreadBy(User $user): self
|
||||
{
|
||||
$this->unreadBy->removeElement($user);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -150,13 +180,6 @@ class Notification
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRead(array $read): self
|
||||
{
|
||||
$this->read = $read;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRelatedEntityClass(string $relatedEntityClass): self
|
||||
{
|
||||
$this->relatedEntityClass = $relatedEntityClass;
|
||||
|
@ -13,25 +13,75 @@ namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\Notification;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\QueryBuilder;
|
||||
use Doctrine\Persistence\ObjectRepository;
|
||||
|
||||
final class NotificationRepository implements ObjectRepository
|
||||
{
|
||||
private EntityManagerInterface $em;
|
||||
|
||||
private EntityRepository $repository;
|
||||
|
||||
public function __construct(EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->em = $entityManager;
|
||||
$this->repository = $entityManager->getRepository(Notification::class);
|
||||
}
|
||||
|
||||
public function countAllForAttendee(User $addressee): int // TODO passer à attendees avec S
|
||||
public function countAllForAttendee(User $addressee): int
|
||||
{
|
||||
$query = $this->queryAllForAttendee($addressee, $countQuery = true);
|
||||
return $this->queryByAddressee($addressee)
|
||||
->select('count(n)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
return $query->getSingleScalarResult();
|
||||
public function countAllForSender(User $sender): int
|
||||
{
|
||||
return $this->queryBySender($sender)
|
||||
->select('count(n)')
|
||||
->getQuery()
|
||||
->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countUnreadByUser(User $user): int
|
||||
{
|
||||
$sql = 'SELECT count(*) AS c FROM chill_main_notification_addresses_unread WHERE user_id = ?';
|
||||
|
||||
$rsm = new Query\ResultSetMapping();
|
||||
$rsm->addScalarResult('c', 'c', Types::INTEGER);
|
||||
|
||||
$nq = $this->em->createNativeQuery($sql, $rsm);
|
||||
|
||||
return $nq->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countUnreadByUserWhereAddressee(User $user): int
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
$qb
|
||||
->select('count(n)')
|
||||
->where($qb->expr()->isMemberOf(':user', 'n.addressees'))
|
||||
->andWhere($qb->expr()->isMemberOf(':user', 'n.unreadBy'))
|
||||
->setParameter('user', $user);
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function countUnreadByUserWhereSender(User $user): int
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
$qb
|
||||
->select('count(n)')
|
||||
->where($qb->expr()->eq('n.sender', ':user'))
|
||||
->andWhere($qb->expr()->isMemberOf(':user', 'n.unreadBy'))
|
||||
->setParameter('user', $user);
|
||||
|
||||
return $qb->getQuery()->getSingleScalarResult();
|
||||
}
|
||||
|
||||
public function find($id, $lockMode = null, $lockVersion = null): ?Notification
|
||||
@ -53,9 +103,9 @@ final class NotificationRepository implements ObjectRepository
|
||||
*
|
||||
* @return Notification[]
|
||||
*/
|
||||
public function findAllForAttendee(User $addressee, $limit = null, $offset = null): array // TODO passer à attendees avec S
|
||||
public function findAllForAttendee(User $addressee, $limit = null, $offset = null): array
|
||||
{
|
||||
$query = $this->queryAllForAttendee($addressee);
|
||||
$query = $this->queryByAddressee($addressee)->select('n');
|
||||
|
||||
if ($limit) {
|
||||
$query = $query->setMaxResults($limit);
|
||||
@ -65,7 +115,22 @@ final class NotificationRepository implements ObjectRepository
|
||||
$query = $query->setFirstResult($offset);
|
||||
}
|
||||
|
||||
return $query->getResult();
|
||||
return $query->getQuery()->getResult();
|
||||
}
|
||||
|
||||
public function findAllForSender(User $sender, $limit = null, $offset = null): array
|
||||
{
|
||||
$query = $this->queryBySender($sender)->select('n');
|
||||
|
||||
if ($limit) {
|
||||
$query = $query->setMaxResults($limit);
|
||||
}
|
||||
|
||||
if ($offset) {
|
||||
$query = $query->setFirstResult($offset);
|
||||
}
|
||||
|
||||
return $query->getQuery()->getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,22 +154,25 @@ final class NotificationRepository implements ObjectRepository
|
||||
return Notification::class;
|
||||
}
|
||||
|
||||
private function queryAllForAttendee(User $addressee, bool $countQuery = false): Query
|
||||
private function queryByAddressee(User $addressee, bool $countQuery = false): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
|
||||
$select = 'n';
|
||||
|
||||
if ($countQuery) {
|
||||
$select = 'count(n)';
|
||||
}
|
||||
|
||||
$qb
|
||||
->select($select)
|
||||
->join('n.addressees', 'a')
|
||||
->where('a = :addressee')
|
||||
->where($qb->expr()->isMemberOf(':addressee', 'n.addressees'))
|
||||
->setParameter('addressee', $addressee);
|
||||
|
||||
return $qb->getQuery();
|
||||
return $qb;
|
||||
}
|
||||
|
||||
private function queryBySender(User $sender): QueryBuilder
|
||||
{
|
||||
$qb = $this->repository->createQueryBuilder('n');
|
||||
|
||||
$qb
|
||||
->where($qb->expr()->eq('n.sender', ':sender'))
|
||||
->setParameter('sender', $sender);
|
||||
|
||||
return $qb;
|
||||
}
|
||||
}
|
||||
|
@ -1,43 +1,65 @@
|
||||
{% extends "@ChillMain/layout.html.twig" %}
|
||||
|
||||
{% block title 'notification.List'|trans %}
|
||||
|
||||
{% block content %}
|
||||
<div id="container content">
|
||||
<div class="grid-8 centered">
|
||||
<h1>{{ "Notifications list" | trans }}</h1>
|
||||
<div class="row">
|
||||
<div class="grid-8 centered">
|
||||
<h1>{{ block('title') }}</h1>
|
||||
|
||||
{%for data in datas %}
|
||||
{% set notification = data.notification %}
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if step == 'inbox' %}active{% endif %}" href="{{ path('chill_main_notification_my') }}">
|
||||
{{ 'notification.Notifications received'|trans }}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads['inbox'] }}
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if step == 'sent' %}active{% endif %}" href="{{ path('chill_main_notification_sent') }}">
|
||||
{{ 'notification.Notifications sent'|trans }}
|
||||
{% if unreads['sent'] > 0 %}
|
||||
<span class="badge rounded-pill bg-danger">
|
||||
{{ unreads['sent'] }}
|
||||
</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Message'|trans }}</dt>
|
||||
<dd>{{ notification.message }}</dd>
|
||||
</dl>
|
||||
{% if datas|length == 0 %}
|
||||
{% if step == 'inbox' %}
|
||||
<p class="chill-no-data-statement">{{ 'notification.Any notification received'|trans }}</p>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ 'notification.Any notification sent'|trans }}</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="flex-table">
|
||||
{% for data in datas %}
|
||||
{% set notification = data.notification %}
|
||||
<div class="item-row">
|
||||
{% include data.template with data.template_data %}
|
||||
</div>
|
||||
<div class="item-row separator">
|
||||
{% if step == 'inbox' %}
|
||||
<div>{{ 'notification.from'|trans }}: {{ notification.sender|chill_entity_render_string }}</div>
|
||||
{% endif %}
|
||||
<div>{{ 'notification.adressees'|trans }}{% for a in notification.addressees %}{{ a|chill_entity_render_string }}{% if not loop.last %}, {% endif %}{% endfor %}</div>
|
||||
<div>{{ notification.date|format_datetime('long', 'short') }}</div>
|
||||
<div>
|
||||
<blockquote class="chill-user-quote">
|
||||
{{ notification.message|u.truncate(250, '…', false)|chill_markdown_to_html }}
|
||||
</blockquote>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Date'|trans }}</dt>
|
||||
<dd>{{ notification.date | date('long') }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Sender'|trans }}</dt>
|
||||
<dd>{{ notification.sender }}</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Addressees'|trans }}</dt>
|
||||
<dd>{{ notification.addressees |join(', ') }}</dd>
|
||||
</dl>
|
||||
|
||||
<dl class="chill_view_data">
|
||||
<dt class="inline">{{ 'Entity'|trans }}</dt>
|
||||
<dd>
|
||||
{% include data.template with data.template_data %}
|
||||
</dd>
|
||||
</dl>
|
||||
{% else %}
|
||||
<p class="chill-no-data-statement">{{ notification.Any notification received }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Chill\Migrations\Main;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
final class Version20211225231532 extends AbstractMigration
|
||||
{
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql('DROP TABLE chill_main_notification_addresses_unread');
|
||||
$this->addSql('ALTER TABLE chill_main_notification ADD read JSONB DEFAULT \'[]\'');
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Store notification readed by user in a specific table';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql('CREATE TABLE chill_main_notification_addresses_unread (notification_id INT NOT NULL, user_id INT NOT NULL, PRIMARY KEY(notification_id, user_id))');
|
||||
$this->addSql('CREATE INDEX IDX_154A075FEF1A9D84 ON chill_main_notification_addresses_unread (notification_id)');
|
||||
$this->addSql('CREATE INDEX IDX_154A075FA76ED395 ON chill_main_notification_addresses_unread (user_id)');
|
||||
$this->addSql('ALTER TABLE chill_main_notification_addresses_unread ADD CONSTRAINT FK_154A075FEF1A9D84 FOREIGN KEY (notification_id) REFERENCES chill_main_notification (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_notification_addresses_unread ADD CONSTRAINT FK_154A075FA76ED395 FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE');
|
||||
$this->addSql('ALTER TABLE chill_main_notification DROP read');
|
||||
}
|
||||
}
|
@ -354,4 +354,8 @@ Created by: Créé par
|
||||
|
||||
notification:
|
||||
Notify: Notifier
|
||||
Notification create: Notification envoyée
|
||||
Notification created: Notification envoyée
|
||||
Any notification received: Aucune notification reçue
|
||||
Any notification sent: Aucune notification envoyée
|
||||
Notifications received: Notifications reçues
|
||||
Notifications sent: Notification envoyées
|
||||
|
@ -187,7 +187,7 @@ class HouseholdMemberController extends ApiController
|
||||
$_format,
|
||||
['groups' => ['read']]
|
||||
);
|
||||
} catch (Exception\InvalidArgumentException | Exception\UnexpectedValueException $e) {
|
||||
} catch (Exception\InvalidArgumentException|Exception\UnexpectedValueException $e) {
|
||||
throw new BadRequestException("Deserialization error: {$e->getMessage()}", 45896, $e);
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
services:
|
||||
Chill\PersonBundle\Notification\AccompanyingPeriodNotificationRenderer:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
Loading…
x
Reference in New Issue
Block a user