Skip to content

Commit 657d132

Browse files
committed
Add: update list endpoint
1 parent 2943771 commit 657d132

6 files changed

Lines changed: 163 additions & 20 deletions

File tree

src/Subscription/Controller/SubscriberListController.php

Lines changed: 112 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
use Doctrine\ORM\EntityManagerInterface;
88
use OpenApi\Attributes as OA;
9-
use PhpList\Core\Domain\Common\Model\Filter\PaginatedFilter;
9+
use PhpList\Core\Domain\Identity\Model\Administrator;
1010
use PhpList\Core\Domain\Messaging\Model\Filter\SubscriberListFilter;
1111
use PhpList\Core\Domain\Subscription\Model\SubscriberList;
1212
use PhpList\Core\Domain\Subscription\Service\Manager\SubscriberListManager;
@@ -32,24 +32,15 @@
3232
#[Route('/lists', name: 'subscriber_list_')]
3333
class SubscriberListController extends BaseController
3434
{
35-
private SubscriberListNormalizer $normalizer;
36-
private SubscriberListManager $subscriberListManager;
37-
private PaginatedDataProvider $paginatedDataProvider;
38-
private EntityManagerInterface $entityManager;
39-
4035
public function __construct(
4136
Authentication $authentication,
4237
RequestValidator $validator,
43-
SubscriberListNormalizer $normalizer,
44-
SubscriberListManager $subscriberListManager,
45-
PaginatedDataProvider $paginatedDataProvider,
46-
EntityManagerInterface $entityManager,
38+
private readonly SubscriberListNormalizer $normalizer,
39+
private readonly SubscriberListManager $subscriberListManager,
40+
private readonly PaginatedDataProvider $paginatedDataProvider,
41+
private readonly EntityManagerInterface $entityManager,
4742
) {
4843
parent::__construct($authentication, $validator);
49-
$this->normalizer = $normalizer;
50-
$this->subscriberListManager = $subscriberListManager;
51-
$this->paginatedDataProvider = $paginatedDataProvider;
52-
$this->entityManager = $entityManager;
5344
}
5445

5546
#[Route('', name: 'get_list', methods: ['GET'])]
@@ -167,19 +158,26 @@ className: SubscriberList::class,
167158
],
168159
type: 'object'
169160
)
170-
)
161+
),
162+
new OA\Response(
163+
response: 405,
164+
description: 'Failure',
165+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
166+
),
171167
]
172168
)]
173169
public function getList(
174170
Request $request,
175171
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null
176172
): JsonResponse {
177-
$this->requireAuthentication($request);
173+
$authUser = $this->requireAuthentication($request);
178174

179175
if (!$list) {
180176
throw $this->createNotFoundException('Subscriber list not found.');
181177
}
182178

179+
$this->denyAccessUnlessOwnerOrPublic($list, $authUser);
180+
183181
return $this->json($this->normalizer->normalize($list), Response::HTTP_OK);
184182
}
185183

@@ -220,19 +218,26 @@ public function getList(
220218
response: 404,
221219
description: 'Failure',
222220
content: new OA\JsonContent(ref: '#/components/schemas/NotFoundErrorResponse')
223-
)
221+
),
222+
new OA\Response(
223+
response: 405,
224+
description: 'Failure',
225+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
226+
),
224227
]
225228
)]
226229
public function deleteList(
227230
Request $request,
228231
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null
229232
): JsonResponse {
230-
$this->requireAuthentication($request);
233+
$authUser = $this->requireAuthentication($request);
231234

232235
if (!$list) {
233236
throw $this->createNotFoundException('Subscriber list not found.');
234237
}
235238

239+
$this->denyAccessUnlessOwnerOrPublic($list, $authUser);
240+
236241
$this->subscriberListManager->delete($list);
237242
$this->entityManager->flush();
238243

@@ -289,4 +294,93 @@ public function createList(Request $request, SubscriberListNormalizer $normalize
289294

290295
return $this->json($normalizer->normalize($data), Response::HTTP_CREATED);
291296
}
297+
298+
#[Route('/{listId}', name: 'update', requirements: ['listId' => '\d+'], methods: ['PUT'])]
299+
#[OA\Post(
300+
path: '/api/v2/lists/{listId}',
301+
description: '🚧 **Status: Beta** – This method is under development. Avoid using in production. ' .
302+
'Returns updated list.',
303+
summary: 'Update a subscriber list.',
304+
requestBody: new OA\RequestBody(
305+
description: 'Pass parameters to create a new subscriber list.',
306+
required: true,
307+
content: new OA\JsonContent(ref: '#/components/schemas/CreateSubscriberListRequest')
308+
),
309+
tags: ['lists'],
310+
parameters: [
311+
new OA\Parameter(
312+
name: 'php-auth-pw',
313+
description: 'Session key obtained from login',
314+
in: 'header',
315+
required: true,
316+
schema: new OA\Schema(type: 'string')
317+
),
318+
new OA\Parameter(
319+
name: 'listId',
320+
description: 'List ID',
321+
in: 'path',
322+
required: true,
323+
schema: new OA\Schema(type: 'string')
324+
),
325+
],
326+
responses: [
327+
new OA\Response(
328+
response: 200,
329+
description: 'Success',
330+
content: new OA\JsonContent(ref: '#/components/schemas/SubscriberList')
331+
),
332+
new OA\Response(
333+
response: 403,
334+
description: 'Failure',
335+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
336+
),
337+
new OA\Response(
338+
response: 405,
339+
description: 'Failure',
340+
content: new OA\JsonContent(ref: '#/components/schemas/UnauthorizedResponse')
341+
),
342+
new OA\Response(
343+
response: 422,
344+
description: 'Failure',
345+
content: new OA\JsonContent(ref: '#/components/schemas/ValidationErrorResponse')
346+
),
347+
]
348+
)]
349+
public function updateList(
350+
Request $request,
351+
SubscriberListNormalizer $normalizer,
352+
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null,
353+
): JsonResponse {
354+
$authUser = $this->requireAuthentication($request);
355+
356+
if (!$list) {
357+
throw $this->createNotFoundException('Subscriber list not found.');
358+
}
359+
360+
$this->denyAccessUnlessOwnerOrPublic($list, $authUser);
361+
362+
/** @var CreateSubscriberListRequest $subscriberListRequest */
363+
$subscriberListRequest = $this->validator->validate($request, CreateSubscriberListRequest::class);
364+
$data = $this->subscriberListManager->updateSubscriberList(
365+
$list,
366+
$subscriberListRequest->getDto(),
367+
$authUser,
368+
);
369+
$this->entityManager->flush();
370+
371+
return $this->json($normalizer->normalize($data), Response::HTTP_OK);
372+
}
373+
374+
private function denyAccessUnlessOwnerOrPublic(SubscriberList $list, Administrator $user): void
375+
{
376+
if ($list->getOwner() === null) {
377+
return;
378+
}
379+
380+
if ($list->getOwner()->getId() === $user->getId()) {
381+
return;
382+
}
383+
384+
throw $this->createAccessDeniedException('Access denied.');
385+
}
292386
}

src/Subscription/Request/CreateSubscriberListRequest.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
new OA\Property(property: 'description', type: 'string', example: 'News (and some fun stuff)'),
1818
new OA\Property(property: 'list_position', type: 'number', example: 12),
1919
new OA\Property(property: 'public', type: 'boolean', example: true),
20+
new OA\Property(property: 'category', type: 'string', example: 'Marketing'),
21+
new OA\Property(property: 'subject_prefix', type: 'string', example: '[News]'),
22+
new OA\Property(property: 'rss_feed', type: 'string', example: 'https://example.com/blog/rss'),
2023
],
2124
type: 'object'
2225
)]
@@ -31,6 +34,9 @@ class CreateSubscriberListRequest implements RequestInterface
3134
public ?int $listPosition = null;
3235

3336
public ?string $description = null;
37+
public ?string $category = null;
38+
public ?string $subjectPrefix = null;
39+
public ?string $rssFeed = null;
3440

3541
public function getDto(): CreateSubscriberListDto
3642
{
@@ -39,6 +45,9 @@ public function getDto(): CreateSubscriberListDto
3945
isPublic: $this->public,
4046
listPosition: $this->listPosition,
4147
description: $this->description,
48+
category: $this->category,
49+
subjectPrefix: $this->subjectPrefix,
50+
rssFeed: $this->rssFeed,
4251
);
4352
}
4453
}

src/Subscription/Serializer/SubscriberListNormalizer.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,10 @@
2121
example: '2022-12-01T10:00:00Z'
2222
),
2323
new OA\Property(property: 'list_position', type: 'integer', example: 1),
24-
new OA\Property(property: 'subject_prefix', type: 'string', example: 'Newsletter: '),
24+
new OA\Property(property: 'subject_prefix', type: 'string', example: '[News]'),
2525
new OA\Property(property: 'public', type: 'boolean', example: true),
26-
new OA\Property(property: 'category', type: 'string', example: 'News'),
26+
new OA\Property(property: 'category', type: 'string', example: 'Marketing'),
27+
new OA\Property(property: 'rss_feed', type: 'string', example: 'https://example.com/blog/rss'),
2728
],
2829
type: 'object'
2930
)]
@@ -47,6 +48,7 @@ public function normalize($object, string $format = null, array $context = []):
4748
'subject_prefix' => $object->getSubjectPrefix(),
4849
'public' => $object->isPublic(),
4950
'category' => $object->getCategory(),
51+
'rss_feed' => $object->getRssFeed(),
5052
];
5153
}
5254

tests/Integration/Subscription/Controller/SubscriberListControllerTest.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function testGetListsWithCurrentSessionKeyReturnsListData()
7676
'subject_prefix' => 'phpList',
7777
'public' => true,
7878
'category' => 'news',
79+
'rss_feed' => null,
7980
],
8081
[
8182
'id' => 2,
@@ -86,6 +87,7 @@ public function testGetListsWithCurrentSessionKeyReturnsListData()
8687
'subject_prefix' => '',
8788
'public' => true,
8889
'category' => '',
90+
'rss_feed' => null,
8991
],
9092
[
9193
'id' => 3,
@@ -96,6 +98,7 @@ public function testGetListsWithCurrentSessionKeyReturnsListData()
9698
'subject_prefix' => '',
9799
'public' => true,
98100
'category' => '',
101+
'rss_feed' => null,
99102
],
100103
],
101104
'pagination' => [
@@ -148,6 +151,7 @@ public function testGetListWithCurrentSessionKeyReturnsListData()
148151
'subject_prefix' => 'phpList',
149152
'public' => true,
150153
'category' => 'news',
154+
'rss_feed' => null,
151155
]
152156
);
153157
}
@@ -279,6 +283,7 @@ public function testGetListMembersWithCurrentSessionKeyForExistingListWithSubscr
279283
'subject_prefix' => '',
280284
'public' => true,
281285
'category' => '',
286+
'rss_feed' => null,
282287
],
283288
],
284289
'history' => [],
@@ -304,6 +309,7 @@ public function testGetListMembersWithCurrentSessionKeyForExistingListWithSubscr
304309
'subject_prefix' => '',
305310
'public' => true,
306311
'category' => '',
312+
'rss_feed' => null,
307313
],
308314
[
309315
'id' => 1,
@@ -314,6 +320,7 @@ public function testGetListMembersWithCurrentSessionKeyForExistingListWithSubscr
314320
'subject_prefix' => 'phpList',
315321
'public' => true,
316322
'category' => 'news',
323+
'rss_feed' => null,
317324
],
318325
],
319326
'history' => [],
@@ -366,4 +373,33 @@ public function testCreateListWithoutSessionKeyReturnsForbidden(): void
366373

367374
$this->assertHttpForbidden();
368375
}
376+
377+
public function testUpdateListWithValidPayloadReturnsUpdatedListData(): void
378+
{
379+
$this->loadFixtures([SubscriberListFixture::class]);
380+
381+
$payload = json_encode([
382+
'name' => 'Updated News',
383+
'description' => 'Updated description',
384+
'listPosition' => 7,
385+
'public' => false,
386+
'category' => 'announcements',
387+
'subjectPrefix' => '[Upd]',
388+
'rssFeed' => 'https://example.com/rss.xml',
389+
]);
390+
391+
$this->authenticatedJsonRequest('PUT', '/api/v2/lists/1', [], [], [], $payload);
392+
393+
$this->assertHttpOkay();
394+
$response = $this->getDecodedJsonResponseContent();
395+
396+
self::assertSame(1, $response['id']);
397+
self::assertSame('Updated News', $response['name']);
398+
self::assertSame('Updated description', $response['description']);
399+
self::assertSame(7, $response['list_position']);
400+
self::assertFalse($response['public']);
401+
self::assertSame('announcements', $response['category']);
402+
self::assertSame('[Upd]', $response['subject_prefix']);
403+
self::assertSame('https://example.com/rss.xml', $response['rss_feed']);
404+
}
369405
}

tests/Unit/Subscription/Serializer/SubscriberListNormalizerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public function testNormalize(): void
4444
'subject_prefix' => 'tech',
4545
'public' => true,
4646
'category' => 'technology',
47+
'rss_feed' => null,
4748
], $result);
4849
}
4950

tests/Unit/Subscription/Serializer/SubscriberNormalizerTest.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function testNormalize(): void
7676
'subject_prefix' => null,
7777
'public' => true,
7878
'category' => '',
79+
'rss_feed' => null,
7980
]
8081
],
8182
'history' => [],

0 commit comments

Comments
 (0)