From 669c36cea16be72eda2ab5015f87d1025a124514 Mon Sep 17 00:00:00 2001 From: matthieu Date: Sun, 15 Mar 2026 19:45:47 +0100 Subject: [PATCH] feat(notification) : add Notification entity, repository, and migration --- migrations/Version20260315184538.php | 38 +++++ src/Entity/Notification.php | 160 ++++++++++++++++++++++ src/Repository/NotificationRepository.php | 46 +++++++ 3 files changed, 244 insertions(+) create mode 100644 migrations/Version20260315184538.php create mode 100644 src/Entity/Notification.php create mode 100644 src/Repository/NotificationRepository.php diff --git a/migrations/Version20260315184538.php b/migrations/Version20260315184538.php new file mode 100644 index 0000000..b2eda8c --- /dev/null +++ b/migrations/Version20260315184538.php @@ -0,0 +1,38 @@ +addSql('CREATE TABLE notification (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, type VARCHAR(50) NOT NULL, title VARCHAR(255) NOT NULL, message TEXT NOT NULL, is_read BOOLEAN NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, user_id INT NOT NULL, related_ticket_id INT DEFAULT NULL, PRIMARY KEY (id))'); + $this->addSql('CREATE INDEX IDX_BF5476CAD8C11BC9 ON notification (related_ticket_id)'); + $this->addSql('CREATE INDEX idx_notification_user ON notification (user_id)'); + $this->addSql('CREATE INDEX idx_notification_user_read ON notification (user_id, is_read)'); + $this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CAA76ED395 FOREIGN KEY (user_id) REFERENCES "user" (id) ON DELETE CASCADE NOT DEFERRABLE'); + $this->addSql('ALTER TABLE notification ADD CONSTRAINT FK_BF5476CAD8C11BC9 FOREIGN KEY (related_ticket_id) REFERENCES client_ticket (id) ON DELETE SET NULL NOT DEFERRABLE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CAA76ED395'); + $this->addSql('ALTER TABLE notification DROP CONSTRAINT FK_BF5476CAD8C11BC9'); + $this->addSql('DROP TABLE notification'); + } +} diff --git a/src/Entity/Notification.php b/src/Entity/Notification.php new file mode 100644 index 0000000..36aefb8 --- /dev/null +++ b/src/Entity/Notification.php @@ -0,0 +1,160 @@ + ['notification:read']], + denormalizationContext: ['groups' => ['notification:write']], + order: ['createdAt' => 'DESC'], +)] +#[ORM\Entity(repositoryClass: NotificationRepository::class)] +#[ORM\Index(columns: ['user_id'], name: 'idx_notification_user')] +#[ORM\Index(columns: ['user_id', 'is_read'], name: 'idx_notification_user_read')] +class Notification +{ + #[ORM\Id] + #[ORM\GeneratedValue] + #[ORM\Column] + #[Groups(['notification:read'])] + private ?int $id = null; + + #[ORM\ManyToOne(targetEntity: User::class)] + #[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')] + #[Groups(['notification:read'])] + private ?User $user = null; + + #[ORM\Column(length: 50)] + #[Groups(['notification:read'])] + private ?string $type = null; + + #[ORM\Column(length: 255)] + #[Groups(['notification:read'])] + private ?string $title = null; + + #[ORM\Column(type: Types::TEXT)] + #[Groups(['notification:read'])] + private ?string $message = null; + + #[ORM\ManyToOne(targetEntity: ClientTicket::class)] + #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')] + #[Groups(['notification:read'])] + private ?ClientTicket $relatedTicket = null; + + #[ORM\Column] + #[Groups(['notification:read', 'notification:write'])] + private bool $isRead = false; + + #[ORM\Column] + #[Groups(['notification:read'])] + private ?DateTimeImmutable $createdAt = null; + + public function getId(): ?int + { + return $this->id; + } + + public function getUser(): ?User + { + return $this->user; + } + + public function setUser(?User $user): static + { + $this->user = $user; + + return $this; + } + + public function getType(): ?string + { + return $this->type; + } + + public function setType(string $type): static + { + $this->type = $type; + + return $this; + } + + public function getTitle(): ?string + { + return $this->title; + } + + public function setTitle(string $title): static + { + $this->title = $title; + + return $this; + } + + public function getMessage(): ?string + { + return $this->message; + } + + public function setMessage(string $message): static + { + $this->message = $message; + + return $this; + } + + public function getRelatedTicket(): ?ClientTicket + { + return $this->relatedTicket; + } + + public function setRelatedTicket(?ClientTicket $relatedTicket): static + { + $this->relatedTicket = $relatedTicket; + + return $this; + } + + public function isRead(): bool + { + return $this->isRead; + } + + public function setIsRead(bool $isRead): static + { + $this->isRead = $isRead; + + return $this; + } + + public function getCreatedAt(): ?DateTimeImmutable + { + return $this->createdAt; + } + + public function setCreatedAt(DateTimeImmutable $createdAt): static + { + $this->createdAt = $createdAt; + + return $this; + } +} diff --git a/src/Repository/NotificationRepository.php b/src/Repository/NotificationRepository.php new file mode 100644 index 0000000..abe47fb --- /dev/null +++ b/src/Repository/NotificationRepository.php @@ -0,0 +1,46 @@ + + */ +class NotificationRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, Notification::class); + } + + public function countUnreadByUser(User $user): int + { + return (int) $this->createQueryBuilder('n') + ->select('COUNT(n.id)') + ->where('n.user = :user') + ->andWhere('n.isRead = false') + ->setParameter('user', $user) + ->getQuery() + ->getSingleScalarResult() + ; + } + + public function markAllReadByUser(User $user): int + { + return $this->createQueryBuilder('n') + ->update() + ->set('n.isRead', 'true') + ->where('n.user = :user') + ->andWhere('n.isRead = false') + ->setParameter('user', $user) + ->getQuery() + ->executeStatement() + ; + } +}