Skip to content

Commit 3a57f7b

Browse files
committed
Add PublicSubscriptionRequest class and update subscribe method in SubscriptionController
1 parent 2b966c2 commit 3a57f7b

12 files changed

Lines changed: 891 additions & 11 deletions

File tree

config/services/services.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ services:
33
autowire: true
44
autoconfigure: true
55

6+
PhpList\RestBundle\Subscription\Service\PublicSubscriptionAttributeRuleProvider:
7+
autowire: true
8+
autoconfigure: true
9+
610
PhpList\Core\Domain\Messaging\Service\ForwardingGuard:
711
autowire: true
812
autoconfigure: true

config/services/validators.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ services:
4242
autoconfigure: true
4343
tags: [ 'validator.constraint_validator' ]
4444

45+
PhpList\RestBundle\Subscription\Validator\Constraint\ValidPublicSubscriptionValidator:
46+
autowire: true
47+
autoconfigure: true
48+
tags: [ 'validator.constraint_validator' ]
49+
4550
PhpList\Core\Domain\Identity\Validator\AttributeTypeValidator:
4651
autowire: true
4752
autoconfigure: true

src/Common/Validator/RequestValidator.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public function __construct(
2020
) {
2121
}
2222

23-
public function validate(Request $request, string $dtoClass): RequestInterface
23+
public function validate(Request $request, string $dtoClass, ?callable $beforeValidation = null): RequestInterface
2424
{
2525
try {
2626
$content = $request->getContent();
@@ -53,6 +53,10 @@ public function validate(Request $request, string $dtoClass): RequestInterface
5353
);
5454
}
5555

56+
if ($beforeValidation !== null) {
57+
$beforeValidation($dto);
58+
}
59+
5660
return $this->validateDto($dto);
5761
}
5862

src/Subscription/Controller/SubscribePageController.php

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
use PhpList\Core\Domain\Common\Model\Filter\PaginatedFilter;
1010
use PhpList\Core\Domain\Identity\Model\PrivilegeFlag;
1111
use PhpList\Core\Domain\Subscription\Model\SubscribePage;
12+
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
1213
use PhpList\Core\Domain\Subscription\Service\Manager\SubscribePageManager;
14+
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberAttributeManager;
15+
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriptionManager;
1316
use PhpList\Core\Security\Authentication;
1417
use PhpList\RestBundle\Common\Controller\BaseController;
1518
use PhpList\RestBundle\Common\Service\Provider\PaginatedDataProvider;
1619
use PhpList\RestBundle\Common\Validator\RequestValidator;
20+
use PhpList\RestBundle\Subscription\Request\PublicSubscriptionRequest;
1721
use PhpList\RestBundle\Subscription\Request\SubscribePageRequest;
1822
use PhpList\RestBundle\Subscription\Serializer\SubscribePageNormalizer;
1923
use PhpList\RestBundle\Subscription\Serializer\SubscribePagePublicNormalizer;
24+
use PhpList\RestBundle\Subscription\Serializer\SubscriptionNormalizer;
2025
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
2126
use Symfony\Component\HttpFoundation\JsonResponse;
2227
use Symfony\Component\HttpFoundation\Request;
@@ -33,6 +38,9 @@ public function __construct(
3338
private readonly SubscribePageNormalizer $normalizer,
3439
private readonly EntityManagerInterface $entityManager,
3540
private readonly PaginatedDataProvider $paginatedProvider,
41+
private readonly SubscriptionManager $subscriptionManager,
42+
private readonly SubscriptionNormalizer $subscriptionNormalizer,
43+
private readonly SubscriberAttributeManager $subscriberAttributeManager,
3644
) {
3745
parent::__construct($authentication, $validator);
3846
}
@@ -425,4 +433,102 @@ public function deletePage(
425433

426434
return $this->json(null, Response::HTTP_NO_CONTENT);
427435
}
436+
437+
#[Route(
438+
'/{id}/lists/{listId}/subscribers',
439+
name: 'subscribe',
440+
requirements: ['listId' => '\d+', 'id' => '\d+'],
441+
methods: ['POST']
442+
)]
443+
#[OA\Post(
444+
path: '/api/v2/subscribe-pages/{id}/lists/{listId}/subscribers',
445+
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production.' .
446+
'Subscribe subscriber to a list from subscribe page.',
447+
summary: 'Create subscription',
448+
requestBody: new OA\RequestBody(
449+
description: '',
450+
required: true,
451+
content: new OA\JsonContent(ref: '#/components/schemas/PublicSubscriptionRequest')
452+
),
453+
tags: ['subscriptions'],
454+
parameters: [
455+
new OA\Parameter(
456+
name: 'listId',
457+
description: 'List ID',
458+
in: 'path',
459+
required: true,
460+
schema: new OA\Schema(type: 'string')
461+
),
462+
new OA\Parameter(
463+
name: 'id',
464+
description: 'Subscribe page ID',
465+
in: 'path',
466+
required: true,
467+
schema: new OA\Schema(type: 'integer')
468+
),
469+
],
470+
responses: [
471+
new OA\Response(
472+
response: 201,
473+
description: 'Success',
474+
content: new OA\JsonContent(
475+
type: 'array',
476+
items: new OA\Items(ref: '#/components/schemas/Subscription')
477+
)
478+
),
479+
new OA\Response(
480+
response: 400,
481+
description: 'Failure',
482+
content: new OA\JsonContent(ref: '#/components/schemas/BadRequestResponse')
483+
),
484+
new OA\Response(
485+
response: 404,
486+
description: 'Failure',
487+
content: new OA\JsonContent(ref: '#/components/schemas/NotFoundErrorResponse')
488+
),
489+
new OA\Response(
490+
response: 422,
491+
description: 'Failure',
492+
content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse')
493+
),
494+
]
495+
)]
496+
public function subscribe(
497+
Request $request,
498+
int $id,
499+
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null,
500+
): JsonResponse {
501+
$page = $this->subscribePageManager->findPublicPage(id: $id);
502+
if (!$list || !$page) {
503+
throw $this->createNotFoundException('Subscriber list or subscribe page not found.');
504+
}
505+
506+
/** @var PublicSubscriptionRequest $subscriptionRequest */
507+
$subscriptionRequest = $this->validator->validate(
508+
request: $request,
509+
dtoClass: PublicSubscriptionRequest::class,
510+
beforeValidation: static function (PublicSubscriptionRequest $dto) use ($page): void {
511+
$dto->setSubscribePage($page);
512+
}
513+
);
514+
$subscriberEmail = $subscriptionRequest->email;
515+
$subscriptions = $this->subscriptionManager->createSubscriptions(
516+
subscriberList: $list,
517+
emails: [$subscriberEmail],
518+
autoConfirm: false,
519+
);
520+
$this->entityManager->flush();
521+
522+
if ($subscriptionRequest->attributes !== []) {
523+
$this->subscriberAttributeManager->processAttributes(
524+
subscriber: $subscriptions[0]->getSubscriber(),
525+
attributeData: $subscriptionRequest->attributes
526+
);
527+
}
528+
$this->entityManager->flush();
529+
530+
$normalized = array_map(fn($item) => $this->subscriptionNormalizer->normalize($item), $subscriptions);
531+
532+
return $this->json($normalized, Response::HTTP_CREATED);
533+
}
428534
}

src/Subscription/Controller/SubscriptionController.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,14 @@
2828
#[Route('/lists', name: 'subscription_')]
2929
class SubscriptionController extends BaseController
3030
{
31-
private SubscriptionManager $subscriptionManager;
32-
private SubscriptionNormalizer $subscriptionNormalizer;
33-
private EntityManagerInterface $entityManager;
34-
3531
public function __construct(
3632
Authentication $authentication,
3733
RequestValidator $validator,
38-
SubscriptionManager $subscriptionManager,
39-
SubscriptionNormalizer $subscriptionNormalizer,
40-
EntityManagerInterface $entityManager,
34+
private readonly SubscriptionManager $subscriptionManager,
35+
private readonly SubscriptionNormalizer $subscriptionNormalizer,
36+
private readonly EntityManagerInterface $entityManager,
4137
) {
4238
parent::__construct($authentication, $validator);
43-
$this->subscriptionManager = $subscriptionManager;
44-
$this->subscriptionNormalizer = $subscriptionNormalizer;
45-
$this->entityManager = $entityManager;
4639
}
4740

4841
#[Route('/{listId}/subscribers', name: 'create', requirements: ['listId' => '\d+'], methods: ['POST'])]
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Subscription\Request;
6+
7+
use OpenApi\Attributes as OA;
8+
use PhpList\Core\Domain\Subscription\Model\SubscribePage;
9+
use PhpList\RestBundle\Common\Request\RequestInterface;
10+
use PhpList\RestBundle\Subscription\Validator\Constraint\ValidPublicSubscription;
11+
use Symfony\Component\Serializer\Attribute\Ignore;
12+
use Symfony\Component\Validator\Constraints as Assert;
13+
14+
#[OA\Schema(
15+
schema: 'PublicSubscriptionRequest',
16+
properties: [
17+
new OA\Property(
18+
property: 'email',
19+
type: 'string',
20+
format: 'email',
21+
example: 'lia@example.com'
22+
),
23+
new OA\Property(
24+
property: 'confirmEmail',
25+
type: 'string',
26+
format: 'email',
27+
example: 'lia@example.com'
28+
),
29+
new OA\Property(
30+
property: 'attributes',
31+
type: 'object',
32+
example: [
33+
'firstname' => 'John',
34+
'lastname' => 'Grigoryan',
35+
'country' => 'Armenia',
36+
],
37+
additionalProperties: true
38+
),
39+
]
40+
)]
41+
#[ValidPublicSubscription]
42+
class PublicSubscriptionRequest implements RequestInterface
43+
{
44+
#[Assert\NotBlank]
45+
#[Assert\Email]
46+
public ?string $email = null;
47+
48+
#[Assert\NotBlank]
49+
#[Assert\Email]
50+
#[Assert\EqualTo(
51+
propertyPath: 'email',
52+
message: 'Email addresses do not match.'
53+
)]
54+
public ?string $confirmEmail = null;
55+
56+
/**
57+
* Key/value pairs matching the subscribe page attributes.
58+
*
59+
* Example:
60+
* [
61+
* 'firstname' => 'John',
62+
* 'lastname' => 'Doe',
63+
* ]
64+
*/
65+
#[Assert\Type('array')]
66+
public array $attributes = [];
67+
68+
#[Ignore]
69+
private ?SubscribePage $subscribePage = null;
70+
71+
public function getDto(): self
72+
{
73+
if ($this->email !== null) {
74+
$this->email = trim($this->email);
75+
}
76+
77+
return $this;
78+
}
79+
80+
public function setSubscribePage(SubscribePage $subscribePage): self
81+
{
82+
$this->subscribePage = $subscribePage;
83+
84+
return $this;
85+
}
86+
87+
public function getSubscribePage(): ?SubscribePage
88+
{
89+
return $this->subscribePage;
90+
}
91+
}

0 commit comments

Comments
 (0)