Skip to content

Commit 3af507f

Browse files
TatevikGrtatevikg1
andauthored
release: dev → main (add forwarding, attachments, open tracking)
New Features Email forwarding endpoint for campaigns/messages. Attachment download endpoint. Open-tracking pixel endpoint to record message opens. Validation for max forward recipients and personal note size. Bug Fixes Improved centralized error handling for delivery/attachment scenarios. API: message serialization no longer exposes message format options. Chores Dependency updates and simplified service/serializer registrations. Co-authored-by: Tatevik <tatevikg1@gmail.com>
1 parent 0cebc96 commit 3af507f

76 files changed

Lines changed: 2596 additions & 327 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.coderabbit.yaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,11 @@ reviews:
99
high_level_summary_in_walkthrough: false
1010
changed_files_summary: false
1111
poem: false
12+
finishing_touches:
13+
docstrings:
14+
enabled: false
1215
auto_review:
1316
enabled: true
1417
base_branches:
1518
- ".*"
1619
drafts: false
17-
18-
checks:
19-
docstring_coverage:
20-
enabled: false

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,11 @@ contribute and how to run the unit tests and style checks locally.
6060
This project adheres to a [Contributor Code of Conduct](CODE_OF_CONDUCT.md).
6161
By participating in this project and its community, you are expected to uphold
6262
this code.
63+
64+
65+
### Code style checks
66+
```bash
67+
vendor/bin/phpstan analyse -l 5 src/ tests/
68+
vendor/bin/phpmd src/ text vendor/phplist/core/config/PHPMD/rules.xml
69+
vendor/bin/phpcs --standard=vendor/phplist/core/config/PhpCodeSniffer/ src/ tests/
70+
```

composer.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,14 @@
4848
"symfony/process": "^6.4",
4949
"zircote/swagger-php": "^4.11",
5050
"ext-dom": "*",
51-
"tatevikgr/rss-feed": "dev-main as 0.1.0"
51+
"tatevikgr/rss-feed": "dev-main as 0.1.0",
52+
"psr/simple-cache": "^3.0",
53+
"symfony/expression-language": "^6.4",
54+
"nelmio/cors-bundle": "^2.4"
5255
},
5356
"require-dev": {
5457
"phpunit/phpunit": "^10.0",
55-
"guzzlehttp/guzzle": "^6.3.0",
58+
"guzzlehttp/guzzle": "^7.2.0",
5659
"squizlabs/php_codesniffer": "^3.2.0",
5760
"phpstan/phpstan": "^1.10",
5861
"nette/caching": "^3.0.0",

config/services/normalizers.yml

Lines changed: 6 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -4,104 +4,16 @@ services:
44
autoconfigure: true
55
public: false
66

7+
_instanceof:
8+
Symfony\Component\Serializer\Normalizer\NormalizerInterface:
9+
tags: [ 'serializer.normalizer' ]
10+
711
Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter: ~
812

913
Symfony\Component\Serializer\Normalizer\ObjectNormalizer:
1014
arguments:
1115
$classMetadataFactory: '@?serializer.mapping.class_metadata_factory'
1216
$nameConverter: '@Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter'
1317

14-
PhpList\RestBundle\Subscription\Serializer\SubscriberNormalizer:
15-
tags: [ 'serializer.normalizer' ]
16-
autowire: true
17-
18-
PhpList\RestBundle\Subscription\Serializer\SubscriberOnlyNormalizer:
19-
tags: [ 'serializer.normalizer' ]
20-
autowire: true
21-
22-
PhpList\RestBundle\Identity\Serializer\AdministratorTokenNormalizer:
23-
tags: [ 'serializer.normalizer' ]
24-
autowire: true
25-
26-
PhpList\RestBundle\Subscription\Serializer\SubscriberListNormalizer:
27-
tags: [ 'serializer.normalizer' ]
28-
autowire: true
29-
30-
PhpList\RestBundle\Subscription\Serializer\SubscriberHistoryNormalizer:
31-
tags: [ 'serializer.normalizer' ]
32-
autowire: true
33-
34-
PhpList\RestBundle\Subscription\Serializer\SubscriptionNormalizer:
35-
tags: [ 'serializer.normalizer' ]
36-
autowire: true
37-
38-
PhpList\RestBundle\Messaging\Serializer\MessageNormalizer:
39-
tags: [ 'serializer.normalizer' ]
40-
autowire: true
41-
42-
PhpList\RestBundle\Messaging\Serializer\TemplateImageNormalizer:
43-
tags: [ 'serializer.normalizer' ]
44-
autowire: true
45-
46-
PhpList\RestBundle\Messaging\Serializer\TemplateNormalizer:
47-
tags: [ 'serializer.normalizer' ]
48-
autowire: true
49-
50-
PhpList\RestBundle\Messaging\Serializer\ListMessageNormalizer:
51-
tags: [ 'serializer.normalizer' ]
52-
autowire: true
53-
54-
PhpList\RestBundle\Identity\Serializer\AdministratorNormalizer:
55-
tags: [ 'serializer.normalizer' ]
56-
autowire: true
57-
58-
PhpList\RestBundle\Identity\Serializer\AdminAttributeDefinitionNormalizer:
59-
tags: [ 'serializer.normalizer' ]
60-
autowire: true
61-
62-
PhpList\RestBundle\Identity\Serializer\AdminAttributeValueNormalizer:
63-
tags: [ 'serializer.normalizer' ]
64-
autowire: true
65-
66-
PhpList\RestBundle\Subscription\Serializer\AttributeDefinitionNormalizer:
67-
tags: [ 'serializer.normalizer' ]
68-
autowire: true
69-
70-
PhpList\RestBundle\Subscription\Serializer\SubscriberAttributeValueNormalizer:
71-
tags: [ 'serializer.normalizer' ]
72-
autowire: true
73-
74-
PhpList\RestBundle\Common\Serializer\CursorPaginationNormalizer:
75-
autowire: true
76-
77-
PhpList\RestBundle\Subscription\Serializer\SubscribersExportRequestNormalizer:
78-
tags: [ 'serializer.normalizer' ]
79-
autowire: true
80-
81-
PhpList\RestBundle\Statistics\Serializer\CampaignStatisticsNormalizer:
82-
tags: [ 'serializer.normalizer' ]
83-
autowire: true
84-
85-
PhpList\RestBundle\Statistics\Serializer\ViewOpensStatisticsNormalizer:
86-
tags: [ 'serializer.normalizer' ]
87-
autowire: true
88-
89-
PhpList\RestBundle\Statistics\Serializer\TopDomainsNormalizer:
90-
tags: [ 'serializer.normalizer' ]
91-
autowire: true
92-
93-
PhpList\RestBundle\Statistics\Serializer\TopLocalPartsNormalizer:
94-
tags: [ 'serializer.normalizer' ]
95-
autowire: true
96-
97-
PhpList\RestBundle\Subscription\Serializer\UserBlacklistNormalizer:
98-
tags: [ 'serializer.normalizer' ]
99-
autowire: true
100-
101-
PhpList\RestBundle\Subscription\Serializer\SubscribePageNormalizer:
102-
tags: [ 'serializer.normalizer' ]
103-
autowire: true
104-
105-
PhpList\RestBundle\Messaging\Serializer\BounceRegexNormalizer:
106-
tags: [ 'serializer.normalizer' ]
107-
autowire: true
18+
PhpList\RestBundle\:
19+
resource: '../../src/*/Serializer/*'

config/services/services.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
11
services:
2-
PhpList\RestBundle\Subscription\Service\SubscriberService:
2+
PhpList\RestBundle\Subscription\Service\SubscriberHistoryService:
33
autowire: true
44
autoconfigure: true
55

6-
PhpList\RestBundle\Subscription\Service\SubscriberHistoryService:
6+
PhpList\Core\Domain\Messaging\Service\ForwardingGuard:
7+
autowire: true
8+
autoconfigure: true
9+
public: false
10+
11+
PhpList\Core\Domain\Messaging\Service\ForwardDeliveryService:
12+
autowire: true
13+
autoconfigure: true
14+
public: false
15+
16+
PhpList\Core\Domain\Messaging\Service\ForwardContentService:
717
autowire: true
818
autoconfigure: true
19+
public: false

config/services/validators.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,13 @@ services:
4141
autowire: true
4242
autoconfigure: true
4343

44+
PhpList\RestBundle\Messaging\Validator\Constraint\MaxForwardCountValidator:
45+
autowire: true
46+
autoconfigure: true
47+
tags: [ 'validator.constraint_validator' ]
48+
49+
PhpList\RestBundle\Messaging\Validator\Constraint\MaxPersonalNoteSizeValidator:
50+
autowire: true
51+
autoconfigure: true
52+
tags: [ 'validator.constraint_validator' ]
53+

src/Common/Dto/CursorPaginationResult.php

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,24 @@
77
class CursorPaginationResult
88
{
99
public function __construct(
10-
public readonly array $items,
11-
public readonly int $limit,
12-
public readonly int $total,
10+
private readonly array $items,
11+
private readonly int $limit,
12+
private readonly int $total,
1313
) {
1414
}
15+
16+
public function getItems(): array
17+
{
18+
return $this->items;
19+
}
20+
21+
public function getLimit(): int
22+
{
23+
return $this->limit;
24+
}
25+
26+
public function getTotal(): int
27+
{
28+
return $this->total;
29+
}
1530
}

src/Common/EventListener/ExceptionListener.php

Lines changed: 40 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66

77
use Exception;
88
use PhpList\Core\Domain\Identity\Exception\AdminAttributeCreationException;
9+
use PhpList\Core\Domain\Messaging\Exception\AttachmentFileNotFoundException;
10+
use PhpList\Core\Domain\Messaging\Exception\MessageNotReceivedException;
11+
use PhpList\Core\Domain\Messaging\Exception\SubscriberNotFoundException;
912
use PhpList\Core\Domain\Subscription\Exception\AttributeDefinitionCreationException;
1013
use PhpList\Core\Domain\Subscription\Exception\SubscriptionCreationException;
1114
use Symfony\Component\HttpFoundation\JsonResponse;
@@ -17,53 +20,49 @@
1720

1821
class ExceptionListener
1922
{
23+
private const EXCEPTION_STATUS_MAP = [
24+
SubscriptionCreationException::class => null,
25+
AttributeDefinitionCreationException::class => null,
26+
AdminAttributeCreationException::class => null,
27+
ValidatorException::class => 400,
28+
AccessDeniedException::class => 403,
29+
AccessDeniedHttpException::class => 403,
30+
AttachmentFileNotFoundException::class => 404,
31+
SubscriberNotFoundException::class => 404,
32+
MessageNotReceivedException::class => 422,
33+
];
34+
2035
public function onKernelException(ExceptionEvent $event): void
2136
{
2237
$exception = $event->getThrowable();
2338

24-
if ($exception instanceof AccessDeniedHttpException) {
25-
$response = new JsonResponse([
26-
'message' => $exception->getMessage(),
27-
], 403);
28-
29-
$event->setResponse($response);
30-
} elseif ($exception instanceof HttpExceptionInterface) {
31-
$response = new JsonResponse([
32-
'message' => $exception->getMessage(),
33-
], $exception->getStatusCode());
39+
foreach (self::EXCEPTION_STATUS_MAP as $class => $statusCode) {
40+
if ($exception instanceof $class) {
41+
$status = $statusCode ?? $exception->getStatusCode();
42+
$event->setResponse(
43+
new JsonResponse([
44+
'message' => $exception->getMessage()
45+
], $status)
46+
);
47+
return;
48+
}
49+
}
3450

35-
$event->setResponse($response);
36-
} elseif ($exception instanceof SubscriptionCreationException) {
37-
$response = new JsonResponse([
38-
'message' => $exception->getMessage(),
39-
], $exception->getStatusCode());
40-
$event->setResponse($response);
41-
} elseif ($exception instanceof AdminAttributeCreationException) {
42-
$response = new JsonResponse([
43-
'message' => $exception->getMessage(),
44-
], $exception->getStatusCode());
45-
$event->setResponse($response);
46-
} elseif ($exception instanceof AttributeDefinitionCreationException) {
47-
$response = new JsonResponse([
48-
'message' => $exception->getMessage(),
49-
], $exception->getStatusCode());
50-
$event->setResponse($response);
51-
} elseif ($exception instanceof ValidatorException) {
52-
$response = new JsonResponse([
53-
'message' => $exception->getMessage(),
54-
], 400);
55-
$event->setResponse($response);
56-
} elseif ($exception instanceof AccessDeniedException) {
57-
$response = new JsonResponse([
58-
'message' => $exception->getMessage(),
59-
], 403);
60-
$event->setResponse($response);
61-
} elseif ($exception instanceof Exception) {
62-
$response = new JsonResponse([
63-
'message' => $exception->getMessage(),
64-
], 500);
51+
if ($exception instanceof HttpExceptionInterface) {
52+
$event->setResponse(
53+
new JsonResponse([
54+
'message' => $exception->getMessage()
55+
], $exception->getStatusCode())
56+
);
57+
return;
58+
}
6559

66-
$event->setResponse($response);
60+
if ($exception instanceof Exception) {
61+
$event->setResponse(
62+
new JsonResponse([
63+
'message' => $exception->getMessage()
64+
], 500)
65+
);
6766
}
6867
}
6968
}

src/Common/Serializer/CursorPaginationNormalizer.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ class CursorPaginationNormalizer implements NormalizerInterface
1515
*/
1616
public function normalize($object, string $format = null, array $context = []): array
1717
{
18-
$items = $object->items;
19-
$limit = $object->limit;
20-
$total = $object->total;
18+
$items = $object->getItems();
19+
$limit = $object->getLimit();
20+
$total = $object->getTotal();
2121
$hasNext = !empty($items) && isset($items[array_key_last($items)]['id']);
2222

2323
return [

src/Common/Service/Provider/PaginatedDataProvider.php

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function getPaginatedList(
2727
Request $request,
2828
NormalizerInterface $normalizer,
2929
string $className,
30-
FilterRequestInterface $filter = null
30+
FilterRequestInterface $filter
3131
): array {
3232
$pagination = $this->paginationFactory->fromRequest($request);
3333

@@ -37,20 +37,22 @@ public function getPaginatedList(
3737
throw new RuntimeException('Repository not found');
3838
}
3939

40-
$items = $repository->getFilteredAfterId(
41-
lastId: $pagination->afterId,
42-
limit: $pagination->limit,
43-
filter: $filter,
44-
);
45-
$total = $repository->count();
40+
$filter->setLimit($pagination->limit);
41+
$filter->setLastId($pagination->afterId);
42+
43+
$result = $repository->getFilteredAfterId(filter: $filter);
4644

4745
$normalizedItems = array_map(
4846
fn($item) => $normalizer->normalize($item, 'json'),
49-
$items
47+
$result->getItems()
5048
);
5149

5250
return $this->paginationNormalizer->normalize(
53-
new CursorPaginationResult($normalizedItems, $pagination->limit, $total)
51+
new CursorPaginationResult(
52+
items: $normalizedItems,
53+
limit: $result->getLimit(),
54+
total: $result->getTotal(),
55+
)
5456
);
5557
}
5658
}

0 commit comments

Comments
 (0)