1
0
Fork 0
mirror of https://github.com/wallabag/wallabag.git synced 2025-06-27 16:36:00 +00:00

add an API endpoint to expose EntryDeletion

This commit is contained in:
Martin Chaine 2025-06-03 09:46:55 +02:00
parent a245434d74
commit b625a77783
No known key found for this signature in database
GPG key ID: 2D04DFDC89D53FDE
5 changed files with 263 additions and 0 deletions

View file

@ -0,0 +1,53 @@
<?php
namespace Wallabag\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Common\DataFixtures\DependentFixtureInterface;
use Doctrine\Persistence\ObjectManager;
use Wallabag\Entity\EntryDeletion;
use Wallabag\Entity\User;
class EntryDeletionFixtures extends Fixture implements DependentFixtureInterface
{
public function load(ObjectManager $manager): void
{
$adminUser = $this->getReference('admin-user', User::class);
$bobUser = $this->getReference('bob-user', User::class);
$deletions = [
[
'user' => $adminUser,
'entry_id' => 1004,
'deleted_at' => new \DateTime('-4 day'),
],
[
'user' => $adminUser,
'entry_id' => 1001,
'deleted_at' => new \DateTime('-1 day'),
],
[
'user' => $bobUser,
'entry_id' => 1003,
'deleted_at' => new \DateTime('-3 days'),
],
];
foreach ($deletions as $deletionData) {
$deletion = new EntryDeletion($deletionData['user'], $deletionData['entry_id']);
$deletion->setDeletedAt($deletionData['deleted_at']);
$manager->persist($deletion);
}
$manager->flush();
}
public function getDependencies(): array
{
return [
UserFixtures::class,
EntryFixtures::class,
];
}
}

View file

@ -0,0 +1,80 @@
<?php
namespace Wallabag\Controller\Api;
use Hateoas\Configuration\Route as HateoasRoute;
use Hateoas\Representation\Factory\PagerfantaFactory;
use OpenApi\Attributes as OA;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Wallabag\Entity\EntryDeletion;
use Wallabag\Repository\EntryDeletionRepository;
use Wallabag\OpenApi\Attribute as WOA;
class EntryDeletionRestController extends WallabagRestController
{
/**
* Retrieve all entry deletions for the current user.
*/
#[Route(path: '/api/entry-deletions.{_format}', name: 'api_get_entry_deletions', methods: ['GET'], defaults: ['_format' => 'json'])]
#[OA\Get(
summary: 'Retrieve all entry deletions for the current user.',
tags: ['EntryDeletions'],
parameters: [
new OA\Parameter(
name: 'since',
in: 'query',
description: 'The timestamp (in seconds) since when you want entry deletions.',
required: false,
schema: new OA\Schema(type: 'integer')
),
new WOA\OrderParameter(),
new WOA\PagerFanta\PageParameter(),
new WOA\PagerFanta\PerPageParameter(default: 100)
],
responses: [
new OA\Response(
response: 200,
description: 'Returned when successful',
content: new WOA\PagerFanta\JsonContent(EntryDeletion::class)
)
]
)]
#[IsGranted('LIST_ENTRIES')]
public function getEntryDeletionsAction(Request $request, EntryDeletionRepository $entryDeletionRepository)
{
$this->validateAuthentication();
$userId = $this->getUser()->getId();
$page = $request->query->get('page', 1);
$perPage = $request->query->get('perPage', 100);
$order = $request->query->get('order', 'desc');
$since = $request->query->get('since');
if (!\in_array($order, ['asc', 'desc'], true)) {
$order = 'desc';
}
$pager = $entryDeletionRepository->findEntryDeletions($userId, $since, $order);
$pager->setMaxPerPage($perPage);
$pager->setCurrentPage($page);
$pagerfantaFactory = new PagerfantaFactory('page', 'perPage');
$paginatedCollection = $pagerfantaFactory->createRepresentation(
$pager,
new HateoasRoute(
'api_get_entry_deletions',
[
'page' => $page,
'perPage' => $perPage,
'order' => $order,
'since' => $since,
],
true
),
);
return $this->sendResponse($paginatedCollection);
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Wallabag\Repository;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Pagerfanta\Doctrine\ORM\QueryAdapter as DoctrineORMAdapter;
use Pagerfanta\Pagerfanta;
use Wallabag\Entity\EntryDeletion;
class EntryDeletionRepository extends ServiceEntityRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, EntryDeletion::class);
}
/**
* Find deletions for a specific user since a given date. The result is paginated.
*
* @param int $userId
* @param int $since
* @param int $page
* @param int $perPage
* @param string $order
*/
public function findEntryDeletions($userId, $since = 0, $order = 'asc'): Pagerfanta
{
$qb = $this->createQueryBuilder('de')
->where('de.user = :userId')->setParameter('userId', $userId)
->orderBy('de.deletedAt', $order);
if ($since > 0) {
$qb->andWhere('de.deletedAt >= :since')
->setParameter('since', new \DateTime(date('Y-m-d H:i:s', $since)));
}
$pagerAdapter = new DoctrineORMAdapter($qb, true, false);
$pager = new Pagerfanta($pagerAdapter);
return $pager;
}
}

View file

@ -0,0 +1,43 @@
<?php
namespace Wallabag\Security\Voter;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Wallabag\Entity\EntryDeletion;
use Wallabag\Entity\User;
class EntryDeletionVoter extends Voter
{
public const VIEW = 'VIEW';
public const LIST = 'LIST';
protected function supports(string $attribute, $subject): bool
{
if (!$subject instanceof EntryDeletion) {
return false;
}
if (!\in_array($attribute, [self::VIEW, self::LIST], true)) {
return false;
}
return true;
}
protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
{
\assert($subject instanceof EntryDeletion);
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
return match ($attribute) {
self::VIEW, self::LIST => $user === $subject->getUser(),
default => false,
};
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace Tests\Wallabag\Controller\Api;
/**
* Test the entry deletion REST API endpoints.
*
* The fixtures set up the following entry deletions:
* - Admin user: 1 deletion from 4 days ago (entry_id: 1004)
* - Admin user: 1 deletion from 1 day ago (entry_id: 1001)
* - Bob user: 1 deletion from 3 days ago (entry_id: 1003)
*
* The logged in user is admin.
*/
class EntryDeletionRestControllerTest extends WallabagApiTestCase
{
public function testGetEntryDeletions()
{
$this->client->request('GET', '/api/entry-deletions');
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
// check that only the items for the current user are returned
$this->assertEquals(2, \count($content['_embedded']['items']));
// validate the deletion schema on the first item
$deletionData = $content['_embedded']['items'][0];
$this->assertArrayHasKey('id', $deletionData);
$this->assertArrayHasKey('entry_id', $deletionData);
$this->assertArrayHasKey('deleted_at', $deletionData);
$this->assertArrayNotHasKey('user_id', $deletionData);
}
public function testGetEntryDeletionsSince()
{
// Test date range filter - get deletions from last 2 days
$since = (new \DateTime('-2 days'))->getTimestamp();
$this->client->request('GET', "/api/entry-deletions?since={$since}");
$this->assertSame(200, $this->client->getResponse()->getStatusCode());
$content = json_decode($this->client->getResponse()->getContent(), true);
$this->assertGreaterThanOrEqual(1, \count($content['_embedded']['items']));
}
}