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:
parent
a245434d74
commit
b625a77783
5 changed files with 263 additions and 0 deletions
53
fixtures/EntryDeletionFixtures.php
Normal file
53
fixtures/EntryDeletionFixtures.php
Normal 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,
|
||||
];
|
||||
}
|
||||
}
|
80
src/Controller/Api/EntryDeletionRestController.php
Normal file
80
src/Controller/Api/EntryDeletionRestController.php
Normal 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);
|
||||
}
|
||||
}
|
43
src/Repository/EntryDeletionRepository.php
Normal file
43
src/Repository/EntryDeletionRepository.php
Normal 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;
|
||||
}
|
||||
}
|
43
src/Security/Voter/EntryDeletionVoter.php
Normal file
43
src/Security/Voter/EntryDeletionVoter.php
Normal 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,
|
||||
};
|
||||
}
|
||||
}
|
44
tests/Controller/Api/EntryDeletionRestControllerTest.php
Normal file
44
tests/Controller/Api/EntryDeletionRestControllerTest.php
Normal 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']));
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue