From c4fba949ce8e98cadb9222a202b9e24b52dcb7f8 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A9r=C3=B4me=20Benoit?= Date: Sun, 1 Jul 2018 17:06:01 +0200 Subject: [PATCH] Add new REST ressources. MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit And some documentation. Signed-off-by: Jérôme Benoit --- .env.dist | 7 + README | 64 +++++++++ composer.json | 1 + composer.lock | 192 ++++++++++++++++++++++++-- config/bundles.php | 1 + config/packages/dev/swiftmailer.yaml | 4 + config/packages/swiftmailer.yaml | 3 + config/packages/test/swiftmailer.yaml | 2 + config/routes.yaml | 3 - src/Controller/PersonController.php | 144 ++++++++++++++++++- src/Entity/Localisation.php | 2 +- symfony.lock | 15 ++ tests/curl.txt | 14 +- 13 files changed, 436 insertions(+), 16 deletions(-) create mode 100644 README create mode 100644 config/packages/dev/swiftmailer.yaml create mode 100644 config/packages/swiftmailer.yaml create mode 100644 config/packages/test/swiftmailer.yaml diff --git a/.env.dist b/.env.dist index 812e66e..b1c4d93 100644 --- a/.env.dist +++ b/.env.dist @@ -15,3 +15,10 @@ APP_SECRET=4c93c568d318d1de3902a70414d41185 # Configure your db driver and server_version in config/packages/doctrine.yaml DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name ###< doctrine/doctrine-bundle ### + +###> symfony/swiftmailer-bundle ### +# For Gmail as a transport, use: "gmail://username:password@localhost" +# For a generic SMTP server, use: "smtp://localhost:25?encryption=&auth_mode=" +# Delivery is disabled by default via "null://localhost" +MAILER_URL=null://localhost +###< symfony/swiftmailer-bundle ### diff --git a/README b/README new file mode 100644 index 0000000..0a3e81e --- /dev/null +++ b/README @@ -0,0 +1,64 @@ +* Installation: +--------------- + + * Symfony: + +We suppose the Symfony framework is already installed with the composer. + +$ git clone https://git.piment-noir.org/Project_proches_de_moi-server.git /path/to/apache_document_root +$ cd /path/to/apache_document_root +$ composer install +$ cp .env.dist .env +$ vi .env + edit the line + DATABASE_URL=mysql://db_user:db_password@127.0.0.1:3306/db_name + to make it match your database installation +$ php/console bin/console doctrine:database:create + + * Apache: + +Virtual localhost configuration: + + + ServerName domain.tld + + DocumentRoot /path/to/apache_document_root/public + + AllowOverride None + Order Allow,Deny + Allow from All + + + Options -MultiViews + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php [QSA,L] + + + + # uncomment the following lines if you install assets as symlinks + # or run into problems when compiling LESS/Sass/CoffeeScript assets + # + # Options FollowSymlinks + # + + # optionally disable the RewriteEngine for the asset directories + # which will allow apache to simply reply with a 404 when files are + # not found instead of passing the request into the full symfony stack + + + RewriteEngine Off + + + ErrorLog ${APACHE_LOG_DIR}/domain.tld_error.log + CustomLog ${APACHE_LOG_DIR}/domain.tld_access.log combined + + # optionally set the value of the environment variables used in the application + #SetEnv APP_ENV prod + #SetEnv APP_SECRET + #SetEnv DATABASE_URL "mysql://db_user:db_pass@host:3306/db_name" + + + * REST resources: + +Open the tests/curl.txt file for an overview. diff --git a/composer.json b/composer.json index fdc5296..dc517c3 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,7 @@ "symfony/lts": "^4@dev", "symfony/security-bundle": "^4.1", "symfony/serializer": "^4.1", + "symfony/swiftmailer-bundle": "^3.2", "symfony/translation": "^4.1", "symfony/twig-bundle": "^4.1", "symfony/yaml": "^4.1" diff --git a/composer.lock b/composer.lock index 28a0f2c..5fd792d 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "9f3d3e3ec394c973255663edcef38cdf", + "content-hash": "cb8518852a624cb243ef9a1ff48e8ba5", "packages": [ { "name": "doctrine/annotations", @@ -793,6 +793,63 @@ ], "time": "2018-02-27T07:30:56+00:00" }, + { + "name": "egulias/email-validator", + "version": "2.1.4", + "source": { + "type": "git", + "url": "https://github.com/egulias/EmailValidator.git", + "reference": "8790f594151ca6a2010c6218e09d96df67173ad3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/8790f594151ca6a2010c6218e09d96df67173ad3", + "reference": "8790f594151ca6a2010c6218e09d96df67173ad3", + "shasum": "" + }, + "require": { + "doctrine/lexer": "^1.0.1", + "php": ">= 5.5" + }, + "require-dev": { + "dominicsayers/isemail": "dev-master", + "phpunit/phpunit": "^4.8.35||^5.7||^6.0", + "satooshi/php-coveralls": "^1.0.1" + }, + "suggest": { + "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Egulias\\EmailValidator\\": "EmailValidator" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Eduardo Gulias Davis" + } + ], + "description": "A library for validating emails against several RFCs", + "homepage": "https://github.com/egulias/EmailValidator", + "keywords": [ + "email", + "emailvalidation", + "emailvalidator", + "validation", + "validator" + ], + "time": "2018-04-10T10:11:19+00:00" + }, { "name": "friendsofsymfony/rest-bundle", "version": "2.3.1", @@ -1479,23 +1536,23 @@ }, { "name": "sensio/framework-extra-bundle", - "version": "v5.1.6", + "version": "v5.2.0", "source": { "type": "git", "url": "https://github.com/sensiolabs/SensioFrameworkExtraBundle.git", - "reference": "bf4940572e43af679aaa13be98f3446a1c237bd8" + "reference": "50e8b7292425957b8fd66887504430c89bcbd83c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/bf4940572e43af679aaa13be98f3446a1c237bd8", - "reference": "bf4940572e43af679aaa13be98f3446a1c237bd8", + "url": "https://api.github.com/repos/sensiolabs/SensioFrameworkExtraBundle/zipball/50e8b7292425957b8fd66887504430c89bcbd83c", + "reference": "50e8b7292425957b8fd66887504430c89bcbd83c", "shasum": "" }, "require": { "doctrine/common": "^2.2", "symfony/config": "^3.3|^4.0", "symfony/dependency-injection": "^3.3|^4.0", - "symfony/framework-bundle": "^3.3|^4.0", + "symfony/framework-bundle": "^3.4|^4.0", "symfony/http-kernel": "^3.3|^4.0" }, "require-dev": { @@ -1505,6 +1562,8 @@ "symfony/dom-crawler": "^3.3|^4.0", "symfony/expression-language": "^3.3|^4.0", "symfony/finder": "^3.3|^4.0", + "symfony/monolog-bridge": "^3.0|^4.0", + "symfony/monolog-bundle": "^3.2", "symfony/phpunit-bridge": "^3.3|^4.0", "symfony/psr-http-message-bridge": "^0.3", "symfony/security-bundle": "^3.3|^4.0", @@ -1521,7 +1580,7 @@ "type": "symfony-bundle", "extra": { "branch-alias": { - "dev-master": "5.1.x-dev" + "dev-master": "5.2.x-dev" } }, "autoload": { @@ -1544,7 +1603,62 @@ "annotations", "controllers" ], - "time": "2018-02-14T08:40:54+00:00" + "time": "2018-05-12T09:37:42+00:00" + }, + { + "name": "swiftmailer/swiftmailer", + "version": "v6.0.2", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/412333372fb6c8ffb65496a2bbd7321af75733fc", + "reference": "412333372fb6c8ffb65496a2bbd7321af75733fc", + "shasum": "" + }, + "require": { + "egulias/email-validator": "~2.0", + "php": ">=7.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.3@dev" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "6.0-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.symfony.com", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2017-09-30T22:39:41+00:00" }, { "name": "symfony/cache", @@ -3008,6 +3122,68 @@ "homepage": "https://symfony.com", "time": "2018-06-22T08:59:39+00:00" }, + { + "name": "symfony/swiftmailer-bundle", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/swiftmailer-bundle.git", + "reference": "f1ba0552a9cd4df0191a58845fbd5541cf9eda2d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/swiftmailer-bundle/zipball/f1ba0552a9cd4df0191a58845fbd5541cf9eda2d", + "reference": "f1ba0552a9cd4df0191a58845fbd5541cf9eda2d", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "swiftmailer/swiftmailer": "^6.0.1", + "symfony/config": "~2.8|~3.3|~4.0", + "symfony/dependency-injection": "~2.7|~3.3|~4.0", + "symfony/http-kernel": "~2.7|~3.3|~4.0" + }, + "require-dev": { + "symfony/console": "~2.7|~3.3|~4.0", + "symfony/framework-bundle": "~2.7|~3.3|~4.0", + "symfony/phpunit-bridge": "~3.3|~4.0", + "symfony/yaml": "~2.7|~3.3|~4.0" + }, + "suggest": { + "psr/log": "Allows logging" + }, + "type": "symfony-bundle", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bundle\\SwiftmailerBundle\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Symfony SwiftmailerBundle", + "homepage": "http://symfony.com", + "time": "2018-04-03T16:29:41+00:00" + }, { "name": "symfony/templating", "version": "v4.1.1", diff --git a/config/bundles.php b/config/bundles.php index 4be7683..27dd46c 100644 --- a/config/bundles.php +++ b/config/bundles.php @@ -10,4 +10,5 @@ return [ Symfony\Bundle\WebServerBundle\WebServerBundle::class => ['all' => true], Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], + Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle::class => ['all' => true], ]; diff --git a/config/packages/dev/swiftmailer.yaml b/config/packages/dev/swiftmailer.yaml new file mode 100644 index 0000000..b98158e --- /dev/null +++ b/config/packages/dev/swiftmailer.yaml @@ -0,0 +1,4 @@ +# See https://symfony.com/doc/current/email/dev_environment.html +swiftmailer: + # send all emails to a specific address + #delivery_addresses: ['me@example.com'] diff --git a/config/packages/swiftmailer.yaml b/config/packages/swiftmailer.yaml new file mode 100644 index 0000000..cae6508 --- /dev/null +++ b/config/packages/swiftmailer.yaml @@ -0,0 +1,3 @@ +swiftmailer: + url: '%env(MAILER_URL)%' + spool: { type: 'memory' } diff --git a/config/packages/test/swiftmailer.yaml b/config/packages/test/swiftmailer.yaml new file mode 100644 index 0000000..f438078 --- /dev/null +++ b/config/packages/test/swiftmailer.yaml @@ -0,0 +1,2 @@ +swiftmailer: + disable_delivery: true diff --git a/config/routes.yaml b/config/routes.yaml index 6538363..c3283aa 100644 --- a/config/routes.yaml +++ b/config/routes.yaml @@ -1,6 +1,3 @@ #index: # path: / # controller: App\Controller\DefaultController::index -person: - type: rest - resource: App\Controller\PersonController diff --git a/src/Controller/PersonController.php b/src/Controller/PersonController.php index b037f53..49c456d 100644 --- a/src/Controller/PersonController.php +++ b/src/Controller/PersonController.php @@ -28,6 +28,7 @@ class PersonController extends FOSRestController $person = new Person(); $person->setFirstname($request->get('firstname')); $person->setLastName($request->get('lastname')); + //TODO: email creation should normally have a verification step $person->setEmail($request->get('email')); $person->setPassword($request->get('password')); $person->setOnline(false); @@ -99,6 +100,7 @@ class PersonController extends FOSRestController $person->setFirstName($request->get('firstname')); $person->setLastName($request->get('lastname')); + //TODO: email update should normally have a verification step $person->setEmail($request->get('email')); $em->merge($person); @@ -107,6 +109,29 @@ class PersonController extends FOSRestController return $this->view($person, Response::HTTP_CREATED, ['Location' => $this->generateUrl('show_person', ['id' => $person->getId(), UrlGeneratorInterface::ABSOLUTE_URL])]); } + /** + * @Rest\Post( + * path = "/api/person/authenticate", + * name = "authenticate_person" + * ) + * @Rest\View(StatusCode = Response::HTTP_ACCEPTED) + */ + public function authenticatePersonAction(Request $request) + { + $em = $this->getDoctrine()->getManager(); + $person = $em->getRepository('App:Person')->findOneBy(['email' => $request->get('email')]); + + if (empty($person)) { + return $this->PersonNotFound(); + } + + if ($request->get('password') != $person->getPassword()) { + return $this->PersonWrongPassword(); + } else { + return $this->view($person, Response::HTTP_ACCEPTED, ['Location' => $this->generateUrl('show_person', ['id' => $person->getId(), UrlGeneratorInterface::ABSOLUTE_URL])]); + } + } + /** * @Rest\Get("/api/person/{id}/localisations") * @Rest\View() @@ -124,6 +149,39 @@ class PersonController extends FOSRestController return $localisations; } + /** + * @Rest\Get( + * path = "/api/person/{id}/localisations/fuzzy/{distance}", + * name = "person_localisations_fuzzy", + * requirements = {"id"="\d+", "distance"="\d+"} + * ) + * @Rest\View() + */ + public function getLocalisationsFuzzyAction(Request $request) + { + //TODO: Check that the authenticated user is allowed to see the localisation + $em = $this->getDoctrine()->getManager(); + $localisations = $em->getRepository('App:Localisation')->findBy(['person' => $request->get('id')]); + + if (empty($localisations)) { + return $this->PersonLocalisationsNotFound(); + } + + if (!$this->chk_distance($request->get('distance'), 200, 500)) { + return $this->PersonLocalisationFuzzyWrongDistance(); + } + + $fuzzy_localisations = array_map(function($item) use ($request) { return $this->randomizeLocation($item, $request->get('distance'), 200, 500); }, $localisations); + + return $fuzzy_localisations; + } + + private function getLastLocalisation($em, $id) { + $query = $em->createQuery("SELECT l1 FROM App\Entity\Localisation l1 WHERE l1.person = :person and l1.timestamp = (SELECT MAX(l2.timestamp) FROM App\Entity\Localisation l2 WHERE l2.person = l1.person)"); + $query->setParameter('person', $id); + return $query->getResult()[0]; + } + /** * @Rest\Get("/api/person/{id}/localisation") * @Rest\View() @@ -133,9 +191,7 @@ class PersonController extends FOSRestController //TODO: Check that the authenticated user is allowed to see the localisation $em = $this->getDoctrine()->getManager(); - $query = $em->createQuery("SELECT l1 FROM App\Entity\Localisation l1 WHERE l1.person = :person and l1.timestamp = (SELECT MAX(l2.timestamp) FROM App\Entity\Localisation l2 WHERE l2.person = l1.person)"); - $query->setParameter('person', $request->get('id')); - $localisation = $query->getResult(); + $localisation = $this->getLastLocalisation($em, $request->get('id')); if (empty($localisation)) { return $this->PersonLocalisationNotFound(); @@ -144,6 +200,61 @@ class PersonController extends FOSRestController return $localisation; } + private function chk_distance($distance, $min, $max) { + if ($distance >= $min && $distance <= $max) { + return true; + } else { + return false; + } + } + + private function randomizeLocation($localisation, $distance, $min, $max) { + // Generate random float in [0, 1[, [0, 1) + $u = rand(0, getrandmax() - 1) / getrandmax(); + $v = rand(0, getrandmax() - 1) / getrandmax(); + + if ($this->chk_distance($distance, $min, $max)) { + $r = $distance / 111300; + } else { + return $this->PersonLocalisationFuzzyWrongDistance(); + } + + $w = $r * sqrt($u); + $t = 2 * pi() * $v; + + $x = $w * cos($t); + $lng_off = $x / cos(deg2rad($localisation->getLatitude())); + $lat_off = $w * sin($t); + + $fuzzy_localisation = new Localisation(); + $fuzzy_localisation->setTimestamp($localisation->getTimestamp()); + $fuzzy_localisation->setLatitude($localisation->getLatitude() + $lat_off); + $fuzzy_localisation->setLongitude($localisation->getLongitude() + $lng_off); + return $fuzzy_localisation; + } + + /** + * @Rest\Get( + * path = "/api/person/{id}/localisation/fuzzy/{distance}", + * name = "person_localisation_fuzzy", + * requirements = {"id"="\d+", "distance"="\d+"} + * ) + * @Rest\View() + */ + public function getLocalisationFuzzyAction(Request $request) + { + //TODO: Check that the authenticated user is allowed to see the localisation + $em = $this->getDoctrine()->getManager(); + + $localisation = $this->getLastLocalisation($em, $request->get('id')); + + if (empty($localisation)) { + return $this->PersonLocalisationNotFound(); + } + + return $this->randomizeLocation($localisation, $request->get('distance'), 200, 500); + } + /** * @Rest\Post("/api/person/{id}/localisation") * @Rest\View(StatusCode = Response::HTTP_CREATED) @@ -211,6 +322,26 @@ class PersonController extends FOSRestController return $person->getFriends(); } + /** + * @Rest\Get( + * path = "/api/person/{id}/friendswithme", + * name = "show_person_friends_with_me", + * requirements = {"id"="\d+"} + * ) + * @Rest\View() + */ + public function showPersonFriendsWithMe(Request $request) + { + $em = $this->getDoctrine()->getManager(); + $person = $em->getRepository('App:Person')->find($request->get('id')); + + if (empty($person)) { + return $this->PersonNotFound(); + } + + return $person->getFriendsWithMe(); + } + /** * @Rest\Get( * path = "/api/persons", @@ -292,4 +423,11 @@ class PersonController extends FOSRestController return View::create(['message' => 'Person localisations not found'], Response::HTTP_NOT_FOUND); } + private function PersonWrongPassword() { + return View::create(['message' => 'Supplied password do not match'], Response::HTTP_UNAUTHORIZED); + } + private function PersonLocalisationFuzzyWrongDistance() { + return View::create(['message' => 'Distance range do not match'], Response::HTTP_NOT_ACCEPTABLE); + } + } diff --git a/src/Entity/Localisation.php b/src/Entity/Localisation.php index 25ee27e..6e86eba 100644 --- a/src/Entity/Localisation.php +++ b/src/Entity/Localisation.php @@ -14,7 +14,7 @@ class Localisation /** * @ORM\Id * @ORM\Column(type="bigint") - * @ORM\GeneratedValue + * @ORM\GeneratedValue(strategy="AUTO") */ protected $id; diff --git a/symfony.lock b/symfony.lock index 6dcf0b2..296c0d1 100644 --- a/symfony.lock +++ b/symfony.lock @@ -44,6 +44,9 @@ "doctrine/orm": { "version": "v2.6.1" }, + "egulias/email-validator": { + "version": "2.1.4" + }, "friendsofsymfony/rest-bundle": { "version": "2.2", "recipe": { @@ -101,6 +104,9 @@ "ref": "aaddfdf43cdecd4cf91f992052d76c2cadc04543" } }, + "swiftmailer/swiftmailer": { + "version": "v6.0.2" + }, "symfony/cache": { "version": "v4.1.0" }, @@ -200,6 +206,15 @@ "symfony/serializer": { "version": "v4.1.1" }, + "symfony/swiftmailer-bundle": { + "version": "2.5", + "recipe": { + "repo": "github.com/symfony/recipes", + "branch": "master", + "version": "2.5", + "ref": "3db029c03e452b4a23f7fc45cec7c922c2247eb8" + } + }, "symfony/templating": { "version": "v4.1.1" }, diff --git a/tests/curl.txt b/tests/curl.txt index 6f2d0ac..c597b4d 100644 --- a/tests/curl.txt +++ b/tests/curl.txt @@ -5,7 +5,10 @@ curl --request POST http://localhost:8000/api/person/register --data "{ \"firstn curl --request DELETE http://localhost:8000/api/person/1 * Update a user with id 1: -curl --request PUT http://localhost:8000/api/person/1 --data "{ \"firstname\": \"Jamesson\", \"lastname\": \"Elbow\", \"email\": \"james@elbow.com\" }" --header "Content-Type: application/json" +curl --request PUT http://localhost:8000/api/person/1 --data "{ \"firstname\": \"Jamesson\", \"lastname\": \"Elbow\", \"email\": \"jamesson@elbow.com\" }" --header "Content-Type: application/json" + +* Authenticate a user with email and password: +curl --request POST http://localhost:8000/api/person/authenticate --data "{ \"email\": \"jamesson@elbow.com\", \"password\": \"FD3CC7719AB1776B81D70B65170D8A51119E127488C106622E0D2E680C1B3FFF\"}" --header "Content-Type: application/json" * Update/add a user with id 1 localisation: curl --request POST http://localhost:8000/api/person/1/localisation --data "{ \"timestamp\": \"$(date --iso-8601=seconds)\", \"latitude\": \"43.23\", \"longitude\": \"5.43\" }" --header "Content-Type: application/json" @@ -20,8 +23,17 @@ curl --request GET http://localhost:8000/api/person/1 * Show a user with id 1 friends: curl --request GET http://localhost:8000/api/person/1/friends +* Show a user with id 1 friends with me: +curl --request GET http://localhost:8000/api/person/1/friendswithme + * Show a user with id 1 localisation: curl --request GET http://localhost:8000/api/person/1/localisation +* Show a user with id 1 fuzzy localisation at a distance of 300 meters: +curl --request GET http://localhost:8000/api/person/1/localisation/fuzzy/300 + * Show a user with id 1 localisations: curl --request GET http://localhost:8000/api/person/1/localisations + +* Show a user with id 1 fuzzy localisations at a distance of 300 meters: +curl --request GET http://localhost:8000/api/person/1/localisations/fuzzy/300 -- 2.34.1