mirror of
https://github.com/wallabag/wallabag.git
synced 2025-06-27 16:36:00 +00:00
introduce the EntryDeletion entity
This commit is contained in:
parent
47a374270a
commit
c3f8d07c92
4 changed files with 225 additions and 0 deletions
37
migrations/Version20250530183653.php
Normal file
37
migrations/Version20250530183653.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Application\Migrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Wallabag\Doctrine\WallabagMigration;
|
||||
|
||||
/**
|
||||
* Add the entry deletion table to keep a history of deleted entries.
|
||||
*/
|
||||
final class Version20250530183653 extends WallabagMigration
|
||||
{
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE TABLE "wallabag_entry_deletion" (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, user_id INTEGER DEFAULT NULL, entry_id INTEGER NOT NULL, deleted_at DATETIME NOT NULL, CONSTRAINT FK_D91765D5A76ED395 FOREIGN KEY (user_id) REFERENCES "wallabag_user" (id) ON DELETE CASCADE NOT DEFERRABLE INITIALLY IMMEDIATE)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_D91765D5A76ED395 ON "wallabag_entry_deletion" (user_id)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_D91765D54AF38FD1 ON "wallabag_entry_deletion" (deleted_at)
|
||||
SQL);
|
||||
$this->addSql(<<<'SQL'
|
||||
CREATE INDEX IDX_D91765D5A76ED3954AF38FD1 ON "wallabag_entry_deletion" (user_id, deleted_at)
|
||||
SQL);
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
$this->addSql(<<<'SQL'
|
||||
DROP TABLE "wallabag_entry_deletion"
|
||||
SQL);
|
||||
}
|
||||
}
|
107
src/Entity/EntryDeletion.php
Normal file
107
src/Entity/EntryDeletion.php
Normal file
|
@ -0,0 +1,107 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use JMS\Serializer\Annotation as Serializer;
|
||||
use Wallabag\Repository\EntryDeletionRepository;
|
||||
|
||||
/**
|
||||
* EntryDeletion.
|
||||
*
|
||||
* Tracks when entries are deleted for client synchronization purposes.
|
||||
*/
|
||||
#[ORM\Table(name: '`entry_deletion`')]
|
||||
#[ORM\Index(columns: ['deleted_at'])]
|
||||
#[ORM\Index(columns: ['user_id', 'deleted_at'])]
|
||||
#[ORM\Entity(repositoryClass: EntryDeletionRepository::class)]
|
||||
class EntryDeletion
|
||||
{
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
#[ORM\Column(name: 'id', type: 'integer')]
|
||||
#[ORM\Id]
|
||||
#[ORM\GeneratedValue(strategy: 'AUTO')]
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
#[ORM\Column(name: 'entry_id', type: 'integer')]
|
||||
private $entryId;
|
||||
|
||||
/**
|
||||
* @var \DateTimeInterface
|
||||
*/
|
||||
#[ORM\Column(name: 'deleted_at', type: 'datetime')]
|
||||
private $deletedAt;
|
||||
|
||||
#[ORM\ManyToOne(targetEntity: User::class)]
|
||||
#[ORM\JoinColumn(name: 'user_id', referencedColumnName: 'id', onDelete: 'CASCADE')]
|
||||
#[Serializer\Exclude()]
|
||||
private $user;
|
||||
|
||||
public function __construct(User $user, int $entryId)
|
||||
{
|
||||
$this->user = $user;
|
||||
$this->entryId = $entryId;
|
||||
$this->deletedAt = new \DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get id.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getId()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get entryId.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getEntryId()
|
||||
{
|
||||
return $this->entryId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \DateTimeInterface
|
||||
*/
|
||||
public function getDeletedAt()
|
||||
{
|
||||
return $this->deletedAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set deleted_at.
|
||||
*
|
||||
* @return EntryDeletion
|
||||
*/
|
||||
public function setDeletedAt(\DateTimeInterface $deletedAt)
|
||||
{
|
||||
$this->deletedAt = $deletedAt;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EntryDeletion from an Entry that's being deleted.
|
||||
*/
|
||||
public static function createFromEntry(Entry $entry): self
|
||||
{
|
||||
return new self($entry->getUser(), $entry->getId());
|
||||
}
|
||||
}
|
36
src/Event/Subscriber/EntryDeletionSubscriber.php
Normal file
36
src/Event/Subscriber/EntryDeletionSubscriber.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace Wallabag\Event\Subscriber;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
use Wallabag\Entity\EntryDeletion;
|
||||
use Wallabag\Event\EntryDeletedEvent;
|
||||
|
||||
class EntryDeletionSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $em,
|
||||
) {
|
||||
}
|
||||
|
||||
public static function getSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
EntryDeletedEvent::NAME => 'onEntryDeleted',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a deletion event record for the entry.
|
||||
*/
|
||||
public function onEntryDeleted(EntryDeletedEvent $event): void
|
||||
{
|
||||
$entry = $event->getEntry();
|
||||
|
||||
$deletionEvent = EntryDeletion::createFromEntry($entry);
|
||||
|
||||
$this->em->persist($deletionEvent);
|
||||
$this->em->flush();
|
||||
}
|
||||
}
|
45
tests/Event/Subscriber/EntryDeletionSubscriberTest.php
Normal file
45
tests/Event/Subscriber/EntryDeletionSubscriberTest.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Wallabag\Event\Subscriber;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Wallabag\Entity\Entry;
|
||||
use Wallabag\Entity\EntryDeletion;
|
||||
use Wallabag\Entity\User;
|
||||
use Wallabag\Event\EntryDeletedEvent;
|
||||
use Wallabag\Event\Subscriber\EntryDeletionSubscriber;
|
||||
|
||||
class EntryDeletionSubscriberTest extends TestCase
|
||||
{
|
||||
public function testEntryDeletionCreatedWhenEntryDeleted(): void
|
||||
{
|
||||
$user = new User();
|
||||
$entry = new Entry($user);
|
||||
|
||||
// the subscriber expects a previously persisted Entry to work
|
||||
$reflectedEntry = new \ReflectionClass($entry);
|
||||
$property = $reflectedEntry->getProperty('id');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($entry, 123);
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
|
||||
// when the event is triggered, an EntryDeletion should be persisted and flushed
|
||||
$em->expects($this->once())
|
||||
->method('persist')
|
||||
->with($this->callback(function ($deletion) use ($entry) {
|
||||
return $deletion instanceof EntryDeletion
|
||||
&& $deletion->getEntryId() === $entry->getId()
|
||||
&& $deletion->getUser() === $entry->getUser();
|
||||
}));
|
||||
$em->expects($this->atLeastOnce())
|
||||
->method('flush');
|
||||
|
||||
// trigger the event to run the mocked up persist and flush
|
||||
/** @var EntityManagerInterface $em */
|
||||
$subscriber = new EntryDeletionSubscriber($em);
|
||||
$event = new EntryDeletedEvent($entry);
|
||||
$subscriber->onEntryDeleted($event);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue