Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] POST subscriptions route #129

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This project adheres to [Semantic Versioning](https://semver.org/).
- Run the system test on Travis (#113)
- Add security headers to the default response (#110)
- Whitelist BadRequestHttpException so that messages are not sanitized (#108)
- REST API endpoint for adding a subscriber to a list

### Changed

Expand Down
70 changes: 70 additions & 0 deletions docs/Api/RestApi.apib
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,73 @@ object containing the following key-value pairs:
"code": 422,
"message": "Some fields invalid: email, confirmed, html_email"
}

# Subscriptions

Resources related to subscriptions.

All requests in this group need to be authenticated with a valid session key
provided as basic auth password. (The basic auth user name can be any string.)

## Subscriptions [/subscriptions]

### Create a new subscription [POST]

Given a valid authentication, this will generate a subscription, which means add a member to a list.
It takes a JSON object containing the following key-value pairs:

+ `subscriber_id` (integer): ID of the subscriber (required)
+ `subscriber_list_id` (integer): ID of the list (required)

+ Response 201 (application/json)

+ Body

{
"creation_date": "2020-01-09T18:44:27+00:00",
}

+ Response 403 (application/json)

+ Body

{
"code": 403,
"message": "No valid session key was provided as basic auth password."
}

+ Response 409 (application/json)

+ Body

{
"code": 409,
"message": "This resource already exists."
}

+ Response 422 (application/json)

+ Body

{
"code": 422,
"message": "Some fields invalid: subscriber_id, subscriber_list_id"
}

+ Response 422 (application/json)

+ Body

{
"code": 422,
"message": "subscriber_id not found: 42"
}

+ Response 422 (application/json)

+ Body

{
"code": 422,
"message": "subscriber_list_id not found: 42"
}
130 changes: 130 additions & 0 deletions src/Controller/SubscriptionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php
declare(strict_types=1);

namespace PhpList\RestBundle\Controller;

use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use FOS\RestBundle\Controller\FOSRestController;
use FOS\RestBundle\Routing\ClassResourceInterface;
use FOS\RestBundle\View\View;
use PhpList\Core\Domain\Model\Subscription\Subscription;
use PhpList\Core\Domain\Repository\Messaging\SubscriberListRepository;
use PhpList\Core\Domain\Repository\Subscription\SubscriberRepository;
use PhpList\Core\Domain\Repository\Subscription\SubscriptionRepository;
use PhpList\Core\Security\Authentication;
use PhpList\RestBundle\Controller\Traits\AuthenticationTrait;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;

/**
* This controller provides REST API access to subscriptions.
*
* @author Matthieu Robin <matthieu@macolu.org>
*/
class SubscriptionController extends FOSRestController implements ClassResourceInterface
{
use AuthenticationTrait;

/**
* @var SubscriberRepository
*/
private $subscriberRepository = null;

/**
* @var SubscriberListRepository
*/
private $subscriberListRepository;

/**
* @var SubscriptionRepository
*/
private $subscriptionRepository;

/**
* @param Authentication $authentication
* @param SubscriberRepository|null $subscriberRepository
* @param SubscriberListRepository $subscriberListRepository
* @param SubscriptionRepository $subscriptionRepository
*/
public function __construct(
Authentication $authentication,
SubscriberRepository $subscriberRepository,
SubscriberListRepository $subscriberListRepository,
SubscriptionRepository $subscriptionRepository
) {
$this->authentication = $authentication;
$this->subscriberRepository = $subscriberRepository;
$this->subscriberListRepository = $subscriberListRepository;
$this->subscriptionRepository = $subscriptionRepository;
}

/**
* Creates a new subscription.
*
* @param Request $request
*
* @return View
*
* @throws UnprocessableEntityHttpException
* @throws ConflictHttpException
*/
public function postAction(Request $request): View
{
$this->requireAuthentication($request);

$this->validateSubscription($request);

$subscriber = $this->subscriberRepository->findOneById($request->get('subscriber_id'));
if ($subscriber === null) {
throw new UnprocessableEntityHttpException(
'subscriber_id not found: '.$request->get('subscriber_id'),
null,
1598917596
);
}

$subscriberList = $this->subscriberListRepository->findOneById($request->get('subscriber_list_id'));
if ($subscriberList === null) {
throw new UnprocessableEntityHttpException(
'subscriber_list_id not found: '.$request->get('subscriber_list_id'),
null,
1598917574
);
}

$subscription = new Subscription();
$subscription->setSubscriber($subscriber);
$subscription->setSubscriberList($subscriberList);

try {
$this->subscriptionRepository->save($subscription);
} catch (UniqueConstraintViolationException $e) {
throw new ConflictHttpException('This resource already exists.', null, 1598918448);
}

return View::create()->setStatusCode(Response::HTTP_CREATED)->setData($subscription);
}

private function validateSubscription(Request $request)
{
/** @var string[] $invalidFields */
$invalidFields = [];
if (filter_var($request->get('subscriber_id'), FILTER_VALIDATE_INT) === false) {
$invalidFields[] = 'subscriber_id';
}

if (filter_var($request->get('subscriber_list_id'), FILTER_VALIDATE_INT) === false) {
$invalidFields[] = 'subscriber_list_id';
}

if (!empty($invalidFields)) {
throw new UnprocessableEntityHttpException(
'Some fields invalid:' . implode(', ', $invalidFields),
null,
1598914359
);
}
}
}