Skip to content

Commit b90c43b

Browse files
committed
ISSUE-345: create message endpoint
1 parent 3c3df09 commit b90c43b

12 files changed

+355
-11
lines changed

config/services/managers.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@ services:
1919
PhpList\RestBundle\Service\Manager\SubscriptionManager:
2020
autowire: true
2121
autoconfigure: true
22+
23+
PhpList\RestBundle\Service\Manager\MessageManager:
24+
autowire: true
25+
autoconfigure: true

src/Controller/CampaignController.php

Lines changed: 129 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
use OpenApi\Attributes as OA;
88
use PhpList\Core\Security\Authentication;
99
use PhpList\RestBundle\Controller\Traits\AuthenticationTrait;
10+
use PhpList\RestBundle\Entity\Request\CreateMessageRequest;
1011
use PhpList\RestBundle\Serializer\MessageNormalizer;
12+
use PhpList\RestBundle\Service\Manager\MessageManager;
1113
use PhpList\RestBundle\Service\Provider\MessageProvider;
1214
use PhpList\RestBundle\Validator\RequestValidator;
1315
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
@@ -17,7 +19,7 @@
1719
use Symfony\Component\Routing\Attribute\Route;
1820

1921
/**
20-
* This controller provides REST API manage campaigns.
22+
* This controller provides REST API to manage campaigns.
2123
*
2224
* @author Tatevik Grigoryan <[email protected]>
2325
*/
@@ -29,17 +31,20 @@ class CampaignController extends AbstractController
2931
private MessageProvider $messageProvider;
3032
private RequestValidator $validator;
3133
private MessageNormalizer $normalizer;
34+
private MessageManager $messageManager;
3235

3336
public function __construct(
3437
Authentication $authentication,
3538
MessageProvider $messageProvider,
3639
RequestValidator $validator,
37-
MessageNormalizer $normalizer
40+
MessageNormalizer $normalizer,
41+
MessageManager $messageManager
3842
) {
3943
$this->authentication = $authentication;
4044
$this->messageProvider = $messageProvider;
4145
$this->validator = $validator;
4246
$this->normalizer = $normalizer;
47+
$this->messageManager = $messageManager;
4348
}
4449

4550
#[Route('', name: 'get_campaigns', methods: ['GET'])]
@@ -86,4 +91,126 @@ public function getMessages(Request $request): JsonResponse
8691

8792
return new JsonResponse($normalized, Response::HTTP_OK);
8893
}
94+
95+
#[Route('', name: 'create_message', methods: ['POST'])]
96+
#[OA\Post(
97+
path: '/campaigns',
98+
description: 'Returns created message.',
99+
summary: 'Create a message for campaign.',
100+
requestBody: new OA\RequestBody(
101+
description: 'Create a new message.',
102+
required: true,
103+
content: new OA\JsonContent(
104+
properties: [
105+
new OA\Property(
106+
property: 'message_content',
107+
properties: [
108+
new OA\Property(property: 'subject', type: 'string', example: 'Campaign Subject'),
109+
new OA\Property(property: 'text', type: 'string', example: 'Full text content'),
110+
new OA\Property(property: 'text_message', type: 'string', example: 'Short text message'),
111+
new OA\Property(property: 'footer', type: 'string', example: 'Unsubscribe link here'),
112+
],
113+
type: 'object'
114+
),
115+
new OA\Property(
116+
property: 'message_format',
117+
properties: [
118+
new OA\Property(property: 'html_formated', type: 'boolean', example: true),
119+
new OA\Property(
120+
property: 'send_format',
121+
type: 'string',
122+
enum: ['html', 'text', 'invite'],
123+
example: 'html'
124+
),
125+
new OA\Property(
126+
property: 'format_options',
127+
type: 'array',
128+
items: new OA\Items(type: 'string', enum: ['text', 'html', 'pdf']),
129+
example: ['html']
130+
),
131+
],
132+
type: 'object'
133+
),
134+
new OA\Property(
135+
property: 'message_metadata',
136+
properties: [
137+
new OA\Property(property: 'status', type: 'string', example: 'draft'),
138+
],
139+
type: 'object'
140+
),
141+
new OA\Property(
142+
property: 'message_schedule',
143+
properties: [
144+
new OA\Property(property: 'repeat_interval', type: 'string', example: '24 hours'),
145+
new OA\Property(
146+
property: 'repeat_until',
147+
type: 'string',
148+
format: 'date-time',
149+
example: '2025-04-30T00:00:00+04:00'
150+
),
151+
new OA\Property(property: 'requeue_interval', type: 'string', example: '12 hours'),
152+
new OA\Property(
153+
property: 'requeue_until',
154+
type: 'string',
155+
format: 'date-time',
156+
example: '2025-04-20T00:00:00+04:00'
157+
),
158+
],
159+
type: 'object'
160+
),
161+
new OA\Property(
162+
property: 'message_options',
163+
properties: [
164+
new OA\Property(property: 'from_field', type: 'string', example: '[email protected]'),
165+
new OA\Property(property: 'to_field', type: 'string', example: '[email protected]'),
166+
new OA\Property(property: 'reply_to', type: 'string', example: '[email protected]'),
167+
new OA\Property(property: 'embargo', type: 'string', example: '2025-04-17 09:00:00'),
168+
new OA\Property(property: 'user_selection', type: 'string', example: 'all-active-users'),
169+
],
170+
type: 'object'
171+
),
172+
],
173+
type: 'object'
174+
)
175+
),
176+
tags: ['campaigns'],
177+
parameters: [
178+
new OA\Parameter(
179+
name: 'session',
180+
description: 'Session ID obtained from authentication',
181+
in: 'header',
182+
required: true,
183+
schema: new OA\Schema(
184+
type: 'string'
185+
)
186+
)
187+
],
188+
responses: [
189+
new OA\Response(
190+
response: 201,
191+
description: 'Success',
192+
content: new OA\JsonContent(ref: '#/components/schemas/Message')
193+
),
194+
new OA\Response(
195+
response: 403,
196+
description: 'Failure',
197+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
198+
),
199+
new OA\Response(
200+
response: 422,
201+
description: 'Failure',
202+
content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse')
203+
),
204+
]
205+
)]
206+
public function createMessage(Request $request, MessageNormalizer $normalizer): JsonResponse
207+
{
208+
$authUser = $this->requireAuthentication($request);
209+
210+
/** @var CreateMessageRequest $createMessageRequest */
211+
$createMessageRequest = $this->validator->validate($request, CreateMessageRequest::class);
212+
$data = $this->messageManager->createMessage($createMessageRequest, $authUser);
213+
214+
return new JsonResponse($normalizer->normalize($data), Response::HTTP_CREATED);
215+
}
89216
}

src/Controller/ListController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,12 @@ public function deleteList(
295295
response: 403,
296296
description: 'Failure',
297297
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
298-
)
298+
),
299+
new OA\Response(
300+
response: 422,
301+
description: 'Failure',
302+
content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse')
303+
),
299304
]
300305
)]
301306
public function createList(Request $request, SubscriberListNormalizer $normalizer): JsonResponse
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request;
6+
7+
use PhpList\RestBundle\Entity\Request\Message\MessageContentRequest;
8+
use PhpList\RestBundle\Entity\Request\Message\MessageFormatRequest;
9+
use PhpList\RestBundle\Entity\Request\Message\MessageMetadataRequest;
10+
use PhpList\RestBundle\Entity\Request\Message\MessageOptionsRequest;
11+
use PhpList\RestBundle\Entity\Request\Message\MessageScheduleRequest;
12+
use Symfony\Component\Validator\Constraints as Assert;
13+
14+
class CreateMessageRequest implements RequestInterface
15+
{
16+
#[Assert\Valid]
17+
#[Assert\NotNull]
18+
public MessageContentRequest $content;
19+
20+
#[Assert\Valid]
21+
#[Assert\NotNull]
22+
public MessageFormatRequest $format;
23+
24+
#[Assert\Valid]
25+
#[Assert\NotNull]
26+
public MessageMetadataRequest $metadata;
27+
28+
#[Assert\Valid]
29+
#[Assert\NotNull]
30+
public MessageScheduleRequest $schedule;
31+
32+
#[Assert\Valid]
33+
#[Assert\NotNull]
34+
public MessageOptionsRequest $options;
35+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request\Message;
6+
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
9+
class MessageContentRequest
10+
{
11+
#[Assert\NotBlank]
12+
public string $subject;
13+
14+
#[Assert\NotBlank]
15+
public string $text;
16+
17+
#[Assert\NotBlank]
18+
public string $textMessage;
19+
20+
#[Assert\NotBlank]
21+
public string $footer;
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request\Message;
6+
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
9+
class MessageFormatRequest
10+
{
11+
#[Assert\Type('bool')]
12+
public bool $htmlFormated;
13+
14+
#[Assert\Choice(['html', 'text', 'invite'])]
15+
public string $sendFormat;
16+
17+
#[Assert\All([
18+
new Assert\Type('string'),
19+
new Assert\Choice(['text', 'html', 'pdf']),
20+
])]
21+
public array $formatOptions;
22+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request\Message;
6+
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
9+
class MessageMetadataRequest
10+
{
11+
#[Assert\NotBlank]
12+
public string $status;
13+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request\Message;
6+
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
9+
class MessageOptionsRequest
10+
{
11+
#[Assert\Email]
12+
public string $fromField;
13+
14+
#[Assert\Email]
15+
public string $toField;
16+
17+
#[Assert\Email]
18+
public ?string $replyTo = null;
19+
20+
#[Assert\NotBlank]
21+
public string $embargo;
22+
23+
public ?string $userSelection = null;
24+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace PhpList\RestBundle\Entity\Request\Message;
6+
7+
use Symfony\Component\Validator\Constraints as Assert;
8+
9+
class MessageScheduleRequest
10+
{
11+
#[Assert\NotBlank]
12+
public int $repeatInterval;
13+
14+
#[Assert\DateTime]
15+
public string $repeatUntil;
16+
17+
#[Assert\NotBlank]
18+
public int $requeueInterval;
19+
20+
#[Assert\DateTime]
21+
public string $requeueUntil;
22+
}

src/OpenApi/SwaggerSchemasEntity.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,19 @@
131131
new OA\Property(
132132
property: 'message_options',
133133
properties: [
134-
new OA\Property(property: 'from_field', type: 'string', example: ' My Name <[email protected]>', nullable: true),
134+
new OA\Property(
135+
property: 'from_field',
136+
type: 'string',
137+
example: ' My Name <[email protected]>',
138+
nullable: true
139+
),
135140
new OA\Property(property: 'to_field', type: 'string', example: '', nullable: true),
136141
new OA\Property(property: 'reply_to', type: 'string', nullable: true),
137142
new OA\Property(property: 'embargo', type: 'string', example: '2023-01-01T12:00:00Z', nullable: true),
138143
new OA\Property(property: 'user_selection', type: 'string', nullable: true),
139144
],
140-
type: 'object'),
145+
type: 'object'
146+
),
141147
],
142148
type: 'object'
143149
)]

src/Serializer/MessageNormalizer.php

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ public function normalize($object, string $format = null, array $context = []):
1818
return [];
1919
}
2020

21+
$formatOptions = array_keys(array_filter([
22+
'text' => $object->getFormat()->isAsText(),
23+
'html' => $object->getFormat()->isAsHtml(),
24+
'pdf' => $object->getFormat()->isAsPdf(),
25+
]));
26+
2127
return [
2228
'id' => $object->getId(),
2329
'unique_id' => $object->getUuid(),
@@ -37,11 +43,7 @@ public function normalize($object, string $format = null, array $context = []):
3743
'message_format' => [
3844
'html_formated' => $object->getFormat()->isHtmlFormatted(),
3945
'send_format' => $object->getFormat()->getSendFormat(),
40-
'as_text' => $object->getFormat()->isAsText(),
41-
'as_html' => $object->getFormat()->isAsHtml(),
42-
'as_pdf' => $object->getFormat()->isAsPdf(),
43-
'as_text_and_html' => $object->getFormat()->isAsTextAndHtml(),
44-
'as_text_and_pdf' => $object->getFormat()->isAsTextAndPdf(),
46+
'format_options' => $formatOptions,
4547
],
4648
'message_metadata' => [
4749
'status' => $object->getMetadata()->getStatus(),
@@ -61,7 +63,7 @@ public function normalize($object, string $format = null, array $context = []):
6163
'from_field' => $object->getOptions()->getFromField(),
6264
'to_field' => $object->getOptions()->getToField(),
6365
'reply_to' => $object->getOptions()->getReplyTo(),
64-
'embargo' => $object->getOptions()->getEmbargo(),
66+
'embargo' => $object->getOptions()->getEmbargo()?->format('Y-m-d\TH:i:sP'),
6567
'user_selection' => $object->getOptions()->getUserSelection(),
6668
],
6769
];

0 commit comments

Comments
 (0)