Skip to content

Commit 937c3ce

Browse files
committed
Subscriber history endpoint
1 parent f80cba4 commit 937c3ce

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;
@@ -20,6 +25,8 @@
2025
use Symfony\Component\HttpFoundation\Response;
2126
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
2227
use Symfony\Component\Routing\Attribute\Route;
28+
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;
29+
use Symfony\Component\Validator\Exception\ValidatorException;
2330

2431
/**
2532
* This controller provides REST API access to subscribers.
@@ -32,17 +39,23 @@ class SubscriberController extends BaseController
3239
{
3340
private SubscriberManager $subscriberManager;
3441
private SubscriberNormalizer $subscriberNormalizer;
42+
private PaginatedDataProvider $paginatedDataProvider;
43+
private NormalizerInterface $serializer;
3544

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

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

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