Skip to content

Commit 34da28c

Browse files
committed
Subscriber history endpoint
1 parent 7d15750 commit 34da28c

File tree

4 files changed

+194
-1
lines changed

4 files changed

+194
-1
lines changed

src/Common/Service/Provider/PaginatedDataProvider.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,11 @@ public function getPaginatedList(
3737
throw new RuntimeException('Repository not found');
3838
}
3939

40-
$items = $repository->getFilteredAfterId($pagination->afterId, $pagination->limit, $filter);
40+
$items = $repository->getFilteredAfterId(
41+
lastId: $pagination->afterId,
42+
limit: $pagination->limit,
43+
filter: $filter,
44+
);
4145
$total = $repository->count();
4246

4347
$normalizedItems = array_map(

src/Subscription/Controller/SubscriberController.php

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,17 @@
44

55
namespace PhpList\RestBundle\Subscription\Controller;
66

7+
use DateTimeImmutable;
8+
use Exception;
79
use OpenApi\Attributes as OA;
810
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
11+
use PhpList\Core\Domain\Subscription\Model\Filter\SubscriberHistoryFilter;
912
use PhpList\Core\Domain\Subscription\Model\Subscriber;
13+
use PhpList\Core\Domain\Subscription\Model\SubscriberHistory;
1014
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberManager;
1115
use PhpList\Core\Security\Authentication;
1216
use PhpList\RestBundle\Common\Controller\BaseController;
17+
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
1318
use PhpList\RestBundle\Common\Validator\RequestValidator;
1419
use PhpList\RestBundle\Subscription\Request\CreateSubscriberRequest;
1520
use PhpList\RestBundle\Subscription\Request\UpdateSubscriberRequest;
@@ -19,6 +24,8 @@
1924
use Symfony\Component\HttpFoundation\Request;
2025
use Symfony\Component\HttpFoundation\Response;
2126
use Symfony\Component\Routing\Attribute\Route;
27+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
28+
use Symfony\Component\Validator\Exception\ValidatorException;
2229

2330
/**
2431
* This controller provides REST API access to subscribers.
@@ -31,17 +38,23 @@ class SubscriberController extends BaseController
3138
{
3239
private SubscriberManager $subscriberManager;
3340
private SubscriberNormalizer $subscriberNormalizer;
41+
private PaginatedDataProvider $paginatedDataProvider;
42+
private NormalizerInterface $serializer;
3443

3544
public function __construct(
3645
Authentication $authentication,
3746
RequestValidator $validator,
3847
SubscriberManager $subscriberManager,
3948
SubscriberNormalizer $subscriberNormalizer,
49+
PaginatedDataProvider $paginatedDataProvider,
50+
NormalizerInterface $serializer,
4051
) {
4152
parent::__construct($authentication, $validator);
4253
$this->authentication = $authentication;
4354
$this->subscriberManager = $subscriberManager;
4455
$this->subscriberNormalizer = $subscriberNormalizer;
56+
$this->paginatedDataProvider = $paginatedDataProvider;
57+
$this->serializer = $serializer;
4558
}
4659

4760
#[Route('', name: 'create', methods: ['POST'])]
@@ -225,6 +238,127 @@ public function getSubscriber(Request $request, int $subscriberId): JsonResponse
225238
return $this->json($this->subscriberNormalizer->normalize($subscriber), Response::HTTP_OK);
226239
}
227240

241+
#[Route('/{subscriberId}/history', name: 'history', requirements: ['subscriberId' => '\d+'], methods: ['GET'])]
242+
#[OA\Get(
243+
path: '/api/v2/subscribers/{subscriberId}/history',
244+
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ',
245+
summary: 'Get subscriber event history',
246+
tags: ['subscribers'],
247+
parameters: [
248+
new OA\Parameter(
249+
name: 'php-auth-pw',
250+
description: 'Session key obtained from login',
251+
in: 'header',
252+
required: true,
253+
schema: new OA\Schema(type: 'string')
254+
),
255+
new OA\Parameter(
256+
name: 'subscriberId',
257+
description: 'Subscriber ID',
258+
in: 'path',
259+
required: true,
260+
schema: new OA\Schema(type: 'integer')
261+
),
262+
new OA\Parameter(
263+
name: 'after_id',
264+
description: 'Page number (pagination)',
265+
in: 'query',
266+
required: false,
267+
schema: new OA\Schema(type: 'integer', default: 1)
268+
),
269+
new OA\Parameter(
270+
name: 'limit',
271+
description: 'Max items per page',
272+
in: 'query',
273+
required: false,
274+
schema: new OA\Schema(type: 'integer', default: 25)
275+
),
276+
new OA\Parameter(
277+
name: 'ip',
278+
description: 'Filter by IP address',
279+
in: 'query',
280+
required: false,
281+
schema: new OA\Schema(type: 'string')
282+
),
283+
new OA\Parameter(
284+
name: 'date_from',
285+
description: 'Filter by date (format: Y-m-d)',
286+
in: 'query',
287+
required: false,
288+
schema: new OA\Schema(type: 'string', format: 'date')
289+
),
290+
new OA\Parameter(
291+
name: 'summery',
292+
description: 'Filter by summary text',
293+
in: 'query',
294+
required: false,
295+
schema: new OA\Schema(type: 'string')
296+
)
297+
],
298+
responses: [
299+
new OA\Response(
300+
response: 200,
301+
description: 'Paginated list of subscriber events',
302+
content: new OA\JsonContent(
303+
properties: [
304+
new OA\Property(
305+
property: 'items',
306+
type: 'array',
307+
items: new OA\Items(ref: '#/components/schemas/SubscriberHistory')
308+
),
309+
new OA\Property(property: 'pagination', ref: '#/components/schemas/CursorPagination')
310+
],
311+
type: 'object'
312+
)
313+
),
314+
new OA\Response(
315+
response: 403,
316+
description: 'Unauthorized',
317+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
318+
),
319+
new OA\Response(
320+
response: 404,
321+
description: 'Not Found',
322+
content: new OA\JsonContent(ref: '#/components/schemas/NotFoundErrorResponse')
323+
)
324+
]
325+
)]
326+
public function getSubscriberHistory(
327+
Request $request,
328+
#[MapEntity(mapping: ['subscriberId' => 'id'])] ?Subscriber $subscriber = null,
329+
): JsonResponse {
330+
$this->requireAuthentication($request);
331+
332+
if (!$subscriber) {
333+
throw $this->createNotFoundException('Subscriber not found.');
334+
}
335+
336+
try {
337+
$dateFrom = $request->query->get('date_from');
338+
$dateFromFormated = $dateFrom ? new DateTimeImmutable($dateFrom) : null;
339+
} catch (Exception $e) {
340+
throw new ValidatorException('Invalid date format. Use format: Y-m-d');
341+
}
342+
343+
$filter = new SubscriberHistoryFilter(
344+
subscriber: $subscriber,
345+
ip: $request->query->get('ip'),
346+
dateFrom: $dateFromFormated,
347+
summery: $request->query->get('summery'),
348+
);
349+
350+
return $this->json(
351+
data: $this->paginatedDataProvider->getPaginatedList(
352+
request: $request,
353+
normalizer: $this->serializer,
354+
className: SubscriberHistory::class,
355+
filter: $filter
356+
),
357+
status: Response::HTTP_OK,
358+
);
359+
}
360+
361+
228362
#[Route('/{subscriberId}', name: 'delete', requirements: ['subscriberId' => '\d+'], methods: ['DELETE'])]
229363
#[OA\Delete(
230364
path: '/api/v2/subscribers/{subscriberId}',

src/Subscription/OpenApi/SwaggerSchemasResponse.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,23 @@
105105
new OA\Property(property: 'value', type: 'string', example: 'United States'),
106106
],
107107
)]
108+
#[OA\Schema(
109+
schema: 'SubscriberHistory',
110+
properties: [
111+
new OA\Property(property: 'id', type: 'integer', example: 1),
112+
new OA\Property(property: 'ip', type: 'string', example: '127.0.0.1'),
113+
new OA\Property(
114+
property: 'created_at',
115+
type: 'string',
116+
format: 'date-time',
117+
example: '2022-12-01T10:00:00Z'
118+
),
119+
new OA\Property(property: 'summery', type: 'string', example: 'Added by admin'),
120+
new OA\Property(property: 'detail', type: 'string', example: 'Added with add-email on test'),
121+
new OA\Property(property: 'system_info', type: 'string', example: 'HTTP_USER_AGENT = Mozilla/5.0'),
122+
],
123+
type: 'object'
124+
)]
108125
class SwaggerSchemasResponse
109126
{
110127
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Subscription\Serializer;
6+
7+
use PhpList\Core\Domain\Subscription\Model\SubscriberHistory;
8+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
9+
10+
class SubscriberHistoryNormalizer implements NormalizerInterface
11+
{
12+
/**
13+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
14+
*/
15+
public function normalize($object, string $format = null, array $context = []): array
16+
{
17+
if (!$object instanceof SubscriberHistory) {
18+
return [];
19+
}
20+
21+
return [
22+
'id' => $object->getId(),
23+
'ip' => $object->getIp(),
24+
'created_at' => $object->getCreatedAt()->format('Y-m-d\TH:i:sP'),
25+
'summery' => $object->getSummary(),
26+
'detail' => $object->getDetail(),
27+
'system_info' => $object->getSystemInfo(),
28+
];
29+
}
30+
31+
/**
32+
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
33+
*/
34+
public function supportsNormalization($data, string $format = null): bool
35+
{
36+
return $data instanceof SubscriberHistory;
37+
}
38+
}

0 commit comments

Comments
 (0)