1
0
Fork 0
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:
Martin Chaine 2025-06-03 09:42:44 +02:00
parent 47a374270a
commit c3f8d07c92
No known key found for this signature in database
GPG key ID: 2D04DFDC89D53FDE
4 changed files with 225 additions and 0 deletions

View 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);
}
}

View 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());
}
}

View 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();
}
}

View 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);
}
}