diff --git a/docs/source/development/api.rst b/docs/source/development/api.rst new file mode 100644 index 000000000..f895be089 --- /dev/null +++ b/docs/source/development/api.rst @@ -0,0 +1,353 @@ +.. Copyright (C) 2014 Champs Libres Cooperative SCRLFS + Permission is granted to copy, distribute and/or modify this document + under the terms of the GNU Free Documentation License, Version 1.3 + or any later version published by the Free Software Foundation; + with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. + A copy of the license is included in the section entitled "GNU + Free Documentation License". + +.. _api + +API +### + +Chill provides a basic framework to build REST api. + +Configure a route +================= + +Follow those steps to build a REST api: + +1. Create your model; +2. Configure the API; + +You can also: + +* hook into the controller to customize some steps; +* add more route and steps + +.. read-also:: + + * `How to use annotation to configure serialization `_ + * `How to create your custom normalizer `_ + +Auto-loading the routes +*********************** + +Ensure that those lines are present in your file `app/config/routing.yml`: + + +.. code-block:: yaml + + chill_cruds: + resource: 'chill_main_crud_route_loader:load' + type: service + + +Create your model +***************** + +Create your model on the usual way: + +.. code-block:: php + + namespace Chill\PersonBundle\Entity\AccompanyingPeriod; + + use Chill\PersonBundle\Entity\AccompanyingPeriod\OriginRepository; + use Doctrine\ORM\Mapping as ORM; + + /** + * @ORM\Entity(repositoryClass=OriginRepository::class) + * @ORM\Table(name="chill_person_accompanying_period_origin") + */ + class Origin + { + /** + * @ORM\Id + * @ORM\GeneratedValue + * @ORM\Column(type="integer") + */ + private $id; + + /** + * @ORM\Column(type="json") + */ + private $label; + + /** + * @ORM\Column(type="date_immutable", nullable=true) + */ + private $noActiveAfter; + + // .. getters and setters + + } + + +Configure api +************* + +Configure the api using Yaml (see the full configuration below): + +.. code-block:: yaml + + # config/packages/chill_main.yaml + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/accompanying-period/origin' + class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin' + name: accompanying_period_origin + base_role: 'ROLE_USER' + actions: + _index: + methods: + GET: true + HEAD: true + _entity: + methods: + GET: true + HEAD: true + +The :code:`_index` and :code:`_entity` action +============================================= + +The :code:`_index` and :code:`_entity` action are default actions: + +* they will call a specific method in the default controller; +* they will generate defined routes: + +Index: + Name: :code:`chill_api_single_accompanying_period_origin__index` + + Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}` + +Entity: + Name: :code:`chill_api_single_accompanying_period_origin__index` + + Path: :code:`/api/1.0/person/accompanying-period/origin.{_format}` + +Role +==== + +By default, the key `base_role` is used to check ACL. Take care of creating the :code:`Voter` required to take that into account. + +For index action, the role will be called with :code:`NULL` as :code:`$subject`. The retrieved entity will be the subject for single queries. + +You can also define a role for each method: + +.. code-block:: yaml + + # config/packages/chill_main.yaml + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/bla/bla' + class: 'Chill\PersonBundle\Entity\Blah' + name: bla + actions: + _entity: + methods: + GET: true + HEAD: true + roles: + GET: MY_ROLE_SEE + HEAD: MY ROLE_SEE + +Customize the controller +======================== + +You can customize the controller by hooking into the default actions. Take care of extending :code:`Chill\MainBundle\CRUD\Controller\ApiController`. + + +.. code-block:: php + + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\ApiController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + + class OpeningApiController extends ApiController + { + protected function customizeQuery(string $action, Request $request, $qb): void + { + $qb->where($qb->expr()->gt('e.noActiveAfter', ':now')) + ->orWhere($qb->expr()->isNull('e.noActiveAfter')); + $qb->setParameter('now', new \DateTime('now')); + } + } + +And set your controller in configuration: + +.. code-block:: yaml + + chill_main: + apis: + accompanying_period_origin: + base_path: '/api/1.0/person/accompanying-period/origin' + class: 'Chill\PersonBundle\Entity\AccompanyingPeriod\Origin' + name: accompanying_period_origin + # add a controller + controller: 'Chill\PersonBundle\Controller\OpeningApiController' + base_role: 'ROLE_USER' + actions: + _index: + methods: + GET: true + HEAD: true + _entity: + methods: + GET: true + HEAD: true + +Create your own actions +======================= + +You can add your own actions: + +.. code-block:: yaml + + chill_main: + apis: + - + class: Chill\PersonBundle\Entity\AccompanyingPeriod + name: accompanying_course + base_path: /api/1.0/person/accompanying-course + controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController + actions: + # add a custom participation: + participation: + methods: + POST: true + DELETE: true + GET: false + HEAD: false + PUT: false + roles: + POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + GET: null + HEAD: null + PUT: null + single-collection: single + +The key :code:`single-collection` with value :code:`single` will add a :code:`/{id}/ + "action name"` (in this example, :code:`/{id}/participation`) into the path, after the base path. If the value is :code:`collection`, no id will be set, but the action name will be append to the path. + +Then, create the corresponding action into your controller: + +.. code-block:: php + + namespace Chill\PersonBundle\Controller; + + use Chill\MainBundle\CRUD\Controller\ApiController; + use Symfony\Component\HttpFoundation\Request; + use Symfony\Component\HttpFoundation\Response; + use Chill\PersonBundle\Entity\AccompanyingPeriod; + use Symfony\Component\HttpFoundation\Exception\BadRequestException; + use Symfony\Component\EventDispatcher\EventDispatcherInterface; + use Symfony\Component\Validator\Validator\ValidatorInterface; + use Chill\PersonBundle\Privacy\AccompanyingPeriodPrivacyEvent; + use Chill\PersonBundle\Entity\Person; + + class AccompanyingCourseApiController extends ApiController + { + protected EventDispatcherInterface $eventDispatcher; + + protected ValidatorInterface $validator; + + public function __construct(EventDispatcherInterface $eventDispatcher, $validator) + { + $this->eventDispatcher = $eventDispatcher; + $this->validator = $validator; + } + + public function participationApi($id, Request $request, $_format) + { + /** @var AccompanyingPeriod $accompanyingPeriod */ + $accompanyingPeriod = $this->getEntity('participation', $id, $request); + $person = $this->getSerializer() + ->deserialize($request->getContent(), Person::class, $_format, []); + + if (NULL === $person) { + throw new BadRequestException('person id not found'); + } + + $this->onPostCheckACL('participation', $request, $accompanyingPeriod, $_format); + + switch ($request->getMethod()) { + case Request::METHOD_POST: + $participation = $accompanyingPeriod->addPerson($person); + break; + case Request::METHOD_DELETE: + $participation = $accompanyingPeriod->removePerson($person); + break; + default: + throw new BadRequestException("This method is not supported"); + } + + $errors = $this->validator->validate($accompanyingPeriod); + + if ($errors->count() > 0) { + // only format accepted + return $this->json($errors); + } + + $this->getDoctrine()->getManager()->flush(); + + return $this->json($participation); + } + } + +.. api_full_config: + +Full configuration example +========================== + +.. code-block:: yaml + + apis: + - + class: Chill\PersonBundle\Entity\AccompanyingPeriod + name: accompanying_course + base_path: /api/1.0/person/accompanying-course + controller: Chill\PersonBundle\Controller\AccompanyingCourseApiController + actions: + _entity: + roles: + GET: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + HEAD: null + POST: null + DELETE: null + PUT: null + controller_action: null + path: null + single-collection: single + methods: + GET: true + HEAD: true + POST: false + DELETE: false + PUT: false + participation: + methods: + POST: true + DELETE: true + GET: false + HEAD: false + PUT: false + roles: + POST: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + DELETE: CHILL_PERSON_ACCOMPANYING_PERIOD_SEE + GET: null + HEAD: null + PUT: null + controller_action: null + # the requirements for the route. Will be set to `[ 'id' => '\d+' ]` if left empty. + requirements: [] + path: null + single-collection: single + base_role: null + +