Skip to content

Commit 469baa1

Browse files
committed
Add: update list endpoint
1 parent 2943771 commit 469baa1

6 files changed

Lines changed: 146 additions & 18 deletions

File tree

src/Subscription/Controller/SubscriberListController.php

Lines changed: 95 additions & 16 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'])]
@@ -174,12 +165,14 @@ public function getList(
174165
Request $request,
175166
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null
176167
): JsonResponse {
177-
$this->requireAuthentication($request);
168+
$authUser = $this->requireAuthentication($request);
178169

179170
if (!$list) {
180171
throw $this->createNotFoundException('Subscriber list not found.');
181172
}
182173

174+
$this->denyAccessUnlessOwnerOrPublic($list, $authUser);
175+
183176
return $this->json($this->normalizer->normalize($list), Response::HTTP_OK);
184177
}
185178

@@ -227,12 +220,14 @@ public function deleteList(
227220
Request $request,
228221
#[MapEntity(mapping: ['listId' => 'id'])] ?SubscriberList $list = null
229222
): JsonResponse {
230-
$this->requireAuthentication($request);
223+
$authUser = $this->requireAuthentication($request);
231224

232225
if (!$list) {
233226
throw $this->createNotFoundException('Subscriber list not found.');
234227
}
235228

229+
$this->denyAccessUnlessOwnerOrPublic($list, $authUser);
230+
236231
$this->subscriberListManager->delete($list);
237232
$this->entityManager->flush();
238233

@@ -289,4 +284,88 @@ public function createList(Request $request, SubscriberListNormalizer $normalize
289284

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

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->assertHttpCreated();
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)