From ddae3c7ed5e3457f88c067ce2bbbb9e5acae4e20 Mon Sep 17 00:00:00 2001 From: Kostas Nikolopoulos Date: Fri, 9 Jun 2017 16:00:51 +0300 Subject: [PATCH 1/2] added user_data and user_data_template endpoints with authorization. --- public/index.php | 46 +++++ src/Controllers/UserDataController.php | 151 +++++++++++++++ .../UserDataTemplateController.php | 65 +++++++ src/Models/UserData.php | 182 ++++++++++++++++++ src/Models/UserDataTemplate.php | 94 +++++++++ .../userData/get-byid-not-found.json | 22 +++ tests/testphase/userData/get-byid.json | 23 +++ tests/testphase/userData/get.json | 26 +++ .../post-failure-missing-template_id.json | 26 +++ .../post-failure-template-not-found.json | 34 ++++ tests/testphase/userData/post.json | 35 ++++ .../testphase/userDataTemplate/get-byid.json | 22 +++ tests/testphase/userDataTemplate/get.json | 25 +++ 13 files changed, 751 insertions(+) create mode 100644 src/Controllers/UserDataController.php create mode 100644 src/Controllers/UserDataTemplateController.php create mode 100644 src/Models/UserData.php create mode 100644 src/Models/UserDataTemplate.php create mode 100644 tests/testphase/userData/get-byid-not-found.json create mode 100644 tests/testphase/userData/get-byid.json create mode 100644 tests/testphase/userData/get.json create mode 100644 tests/testphase/userData/post-failure-missing-template_id.json create mode 100644 tests/testphase/userData/post-failure-template-not-found.json create mode 100644 tests/testphase/userData/post.json create mode 100644 tests/testphase/userDataTemplate/get-byid.json create mode 100644 tests/testphase/userDataTemplate/get.json diff --git a/public/index.php b/public/index.php index 041adc6..00ca34c 100644 --- a/public/index.php +++ b/public/index.php @@ -95,6 +95,36 @@ 'byIdRelationships', Phramework::METHOD_ANY ], + [ + 'user_data_template/', //URI + NS . 'UserDataTemplateController', //Class + 'GET', //Class method + Phramework::METHOD_GET, //HTTP Method + ], + [ + 'user_data_template/{id}', //URI + NS . 'UserDataTemplateController', //Class + 'GETById', //Class method + Phramework::METHOD_GET, //HTTP Method + ], + [ + 'user_data/', //URI + NS . 'UserDataController', //Class + 'GET', //Class method + Phramework::METHOD_GET, //HTTP Method + ], + [ + 'user_data/{id}', //URI + NS . 'UserDataController', //Class + 'GETById', //Class method + Phramework::METHOD_GET, //HTTP Method + ], + [ + 'user_data/', //URI + NS . 'UserDataController', //Class + 'POST', //Class method + Phramework::METHOD_POST, //HTTP Method + ], ]); //Initialize API with settings and routing @@ -109,6 +139,22 @@ Phramework::setViewer( \Phramework\JSONAPI\Viewers\JSONAPI::class ); + //Set authentication class + \Phramework\Authentication\Manager::register( + \Phramework\Authentication\BasicAuthentication\BasicAuthentication::class + ); + + //Set method to fetch user object, including password attribute + \Phramework\Authentication\Manager::setUserGetByEmailMethod( + [ + \Phramework\Examples\JSONAPI\Models\Administrator\Authentication::class, + 'getByEmailWithPassword' + ] + ); + + \Phramework\Authentication\Manager::setAttributes( + ['email'] + ); unset($settings); diff --git a/src/Controllers/UserDataController.php b/src/Controllers/UserDataController.php new file mode 100644 index 0000000..1780a21 --- /dev/null +++ b/src/Controllers/UserDataController.php @@ -0,0 +1,151 @@ + + */ +class UserDataController extends \Phramework\Examples\JSONAPI\Controller +{ + /** + * Get collection + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + */ + public static function GET($params, $method, $headers) + { + $user = Request::checkPermission(); + + static::handleGET( + $params, + UserData::class, + [$user->id], + [] + ); + } + + /** + * Get a resource + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + * @param string $id Resource id + */ + public static function GETById($params, $method, $headers, string $id) + { + $user = Request::checkPermission(); + + static::handleGETById( + $params, + $id, + UserData::class, + [$user->id], + [] + ); + } + + /** + * Post new resource + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + */ + public static function POST(\stdClass $params, string $method, array $headers) + { + $user = Request::checkPermission(); + + static::handlePOST( + $params, + $method, + $headers, + UserData::class, + [], + [], + [ + /** + * A validation callback to inject user id + */ + function ( + \stdClass $requestAttributes, + \stdClass $requestRelationships, + \stdClass $attributes, + \stdClass $parsedRelationshipAttributes + ) use ($user) { + $attributes->user_id = $user->id; + } + ], + /** + * Override view, by setting a view callback + * to response with 204 and a Location header + */ + function (array $ids) { + //Prepare response with 201 Created status code and Location header + \Phramework\Models\Response::created( + UserData::getSelfLink($ids[0]) + ); + \Phramework\JSONAPI\Viewers\JSONAPI::header(); + //Will overwrite 201 with 204 status code + \Phramework\Models\Response::noContent(); + } + ); + } + + + + /** + * Manage resource's relationships + * `/article/{id}/relationships/{relationship}/` handler + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + * @param string $id Resource id + * @param string $relationship Relationship + */ + public static function byIdRelationships( + $params, + $method, + $headers, + string $id, + string $relationship + ) { + static::handleByIdRelationships( + $params, + $method, + $headers, + $id, + $relationship, + UserData::class, + [Phramework::METHOD_GET], + [], + [] + ); + } + + + + + + +} diff --git a/src/Controllers/UserDataTemplateController.php b/src/Controllers/UserDataTemplateController.php new file mode 100644 index 0000000..a697c14 --- /dev/null +++ b/src/Controllers/UserDataTemplateController.php @@ -0,0 +1,65 @@ + + */ +class UserDataTemplateController extends \Phramework\Examples\JSONAPI\Controller +{ + /** + * Get collection + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + */ + public static function GET($params, $method, $headers) + { + static::handleGET( + $params, + UserDataTemplate::class, + [], + [] + ); + } + + /** + * Get a resource + * @param \stdClass $params Request parameters + * @param string $method Request method + * @param array $headers Request headers + * @param string $id Resource id + */ + public static function GETById($params, $method, $headers, string $id) + { + static::handleGETById( + $params, + $id, + UserDataTemplate::class, + [], + [] + ); + } + + +} diff --git a/src/Models/UserData.php b/src/Models/UserData.php new file mode 100644 index 0000000..ec711c7 --- /dev/null +++ b/src/Models/UserData.php @@ -0,0 +1,182 @@ + + */ +class UserData extends \Phramework\Examples\JSONAPI\Model +{ + protected static $type = 'user_data'; + protected static $endpoint = 'user_data'; + protected static $table = 'user_data'; + + /** + * @param Page $page + * @param Filter $filter + * @param Sort $sort + * @param Fields $fields + * @param mixed ...$additionalParameters + * @return Resource[] + */ + public static function get( + Page $page = null, + Filter $filter = null, + Sort $sort = null, + Fields $fields = null, + ...$additionalParameters + ) { + $query = static::handleGet( + 'SELECT + {{fields}} + FROM "user_data" + WHERE + "user_id" = ? + {{filter}} + {{sort}} + {{page}}', + $page, + $filter, + $sort, + $fields, + true + ); + + $records = Database::executeAndFetchAll( + $query, + [ $additionalParameters[0] ] + ); + + array_walk( + $records, + [static::class, 'prepareRecord'] + ); + + return static::collection($records, $fields); + } + + /** + * Defines model's validator for POST requests + * also may be used for PATCH requests and to validate filter directive values + * @return ValidationModel + */ + public static function getValidationModel() + { + return new ValidationModel( + new ObjectValidator( //attributes + (object) [ //properties + 'value' => new ObjectValidator((object)[], [], true), + ], + ['value'], //required attributes, + false //additional properties + ), + new ObjectValidator( //relationships + (object) [ + 'user' => User::getIdValidator(), + 'template'=> UserDataTemplate::getIdValidator(), + ], + ['template'], //required relationships, + false //additional properties + ) + ); + } + + /** + * @return string[] + */ + public static function getFields() + { + return ['user_id', 'user_data_template_id', 'value']; + } + + /** + * Override post behaviour, + * to encode json value to string + * @param \stdClass|array $attributes + * @return string + */ + public static function post( + $attributes, + $return = \Phramework\Database\Operations\Create::RETURN_ID + ) { + /* + * Work with object + */ + + if (is_array($attributes)) { + $attributes = (object) $attributes; + } + + if (isset($attributes->value)) { + $attributes->value = json_encode($attributes->value); + } + + return parent::post($attributes, $return); + } + + /** + * @return \stdClass + */ + public static function getRelationships() + { + return (object) [ + 'user' => new Relationship( + User::class, + Relationship::TYPE_TO_ONE, + 'user_id', //source data attribute + null, //source data callback + Relationship::FLAG_DEFAULT | Relationship::FLAG_DATA + ), + 'template' => new Relationship( + UserDataTemplate::class, + Relationship::TYPE_TO_ONE, + 'user_data_template_id', //source data attribute + null, + Relationship::FLAG_DEFAULT | Relationship::FLAG_DATA + ) + ]; + } + + /** + * Prepare records + * @param array $record Database record row + * @return array|null on failure + */ + protected static function prepareRecord(array &$record) + { + if (isset($record['value'])) { + $record['value'] = json_decode($record['value']); + } + } +} diff --git a/src/Models/UserDataTemplate.php b/src/Models/UserDataTemplate.php new file mode 100644 index 0000000..49fc5e7 --- /dev/null +++ b/src/Models/UserDataTemplate.php @@ -0,0 +1,94 @@ + + */ +class UserDataTemplate extends \Phramework\Examples\JSONAPI\Model +{ + protected static $type = 'user_data_template'; + protected static $endpoint = 'user_data_template'; + protected static $table = 'user_data_template'; + + /** + * @param Page $page + * @param Filter $filter + * @param Sort $sort + * @param Fields $fields + * @param mixed ...$additionalParameters + * @return Resource[] + */ + public static function get( + Page $page = null, + Filter $filter = null, + Sort $sort = null, + Fields $fields = null, + ...$additionalParameters + ) { + $query = static::handleGet( + 'SELECT + {{fields}} + FROM "user_data_template" + {{filter}} + {{sort}} + {{page}}', + $page, + $filter, + $sort, + $fields, + false + ); + + $records = Database::executeAndFetchAll( + $query, + [ ] + ); + + array_walk( + $records, + [static::class, 'prepareRecord'] + ); + + return static::collection($records, $fields); + } + + /** + * @return string[] + */ + public static function getFields() + { + return ['value']; + } + +} diff --git a/tests/testphase/userData/get-byid-not-found.json b/tests/testphase/userData/get-byid-not-found.json new file mode 100644 index 0000000..aba456c --- /dev/null +++ b/tests/testphase/userData/get-byid-not-found.json @@ -0,0 +1,22 @@ +{ + "meta": { + "order": 122 + }, + "request": { + "url": "user_data/333", + "method": "GET", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ] + }, + "response": { + "statusCode": 404, + "headers": { + "Content-Type": "{{{headerResponseContentType}}}" + }, + "ruleObjects": [ + ] + } +} \ No newline at end of file diff --git a/tests/testphase/userData/get-byid.json b/tests/testphase/userData/get-byid.json new file mode 100644 index 0000000..4fb1363 --- /dev/null +++ b/tests/testphase/userData/get-byid.json @@ -0,0 +1,23 @@ +{ + "meta": { + "order": 121 + }, + "request": { + "url": "user_data/{{userDataId1}}", + "method": "GET", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ] + }, + "response": { + "statusCode": 200, + "headers": { + "Content-Type": "{{{headerResponseContentType}}}" + }, + "ruleObjects": [ + "{{{responseBodyJsonapiResource}}}" + ] + } +} \ No newline at end of file diff --git a/tests/testphase/userData/get.json b/tests/testphase/userData/get.json new file mode 100644 index 0000000..70ad7fb --- /dev/null +++ b/tests/testphase/userData/get.json @@ -0,0 +1,26 @@ +{ + "meta": { + "order": 120 + }, + "request": { + "url": "user_data/", + "method": "GET", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ] + }, + "response": { + "statusCode": 200, + "headers": { + "Content-Type": "{{{headerResponseContentType}}}" + }, + "ruleObjects": [ + "{{{responseBodyJsonapiCollection}}}" + ], + "export": { + "userDataId1": "data.id" + } + } +} \ No newline at end of file diff --git a/tests/testphase/userData/post-failure-missing-template_id.json b/tests/testphase/userData/post-failure-missing-template_id.json new file mode 100644 index 0000000..ce0d53f --- /dev/null +++ b/tests/testphase/userData/post-failure-missing-template_id.json @@ -0,0 +1,26 @@ +{ + "meta": { + "order": 124, + "description": "Attempt to create a new user data, expecting exception since body is without a \"user data template \" relationship" + }, + "request": { + "url": "user_data", + "method": "POST", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ], + "body": { + "data": { + "type": "user_data", + "attributes": { + "value": {"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}} + } + } + } + }, + "response": { + "statusCode": 422 + } +} \ No newline at end of file diff --git a/tests/testphase/userData/post-failure-template-not-found.json b/tests/testphase/userData/post-failure-template-not-found.json new file mode 100644 index 0000000..cabf7ee --- /dev/null +++ b/tests/testphase/userData/post-failure-template-not-found.json @@ -0,0 +1,34 @@ +{ + "meta": { + "order": 125, + "description": "Attempt to create a new user data, expecting exception since using non-existing user_data_template" + }, + "request": { + "url": "user_data", + "method": "POST", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ], + "body": { + "data": { + "type": "user_data", + "attributes": { + "value": {"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}} + }, + "relationships": { + "template": { + "data": { + "id": "1234567890", + "type": "user_data_template" + } + } + } + } + } + }, + "response": { + "statusCode": 404 + } +} \ No newline at end of file diff --git a/tests/testphase/userData/post.json b/tests/testphase/userData/post.json new file mode 100644 index 0000000..7df157b --- /dev/null +++ b/tests/testphase/userData/post.json @@ -0,0 +1,35 @@ +{ + "meta": { + "order": 123, + "description": "Attempt to create a new user data successfully" + }, + "request": { + "url": "user_data", + "method": "POST", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}", + "{{{headerRequestAuthorization}}}" + ], + "body": { + "data": { + "type": "user_data", + "attributes": { + "value": {"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}} + + }, + "relationships": { + "template": { + "data": { + "id": "1", + "type": "user_data_template" + } + } + } + } + } + }, + "response": { + "statusCode": 204 + } +} \ No newline at end of file diff --git a/tests/testphase/userDataTemplate/get-byid.json b/tests/testphase/userDataTemplate/get-byid.json new file mode 100644 index 0000000..0d7380f --- /dev/null +++ b/tests/testphase/userDataTemplate/get-byid.json @@ -0,0 +1,22 @@ +{ + "meta": { + "order": 101 + }, + "request": { + "url": "user_data_template/{{templateId1}}", + "method": "GET", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}" + ] + }, + "response": { + "statusCode": 200, + "headers": { + "Content-Type": "{{{headerResponseContentType}}}" + }, + "ruleObjects": [ + "{{{responseBodyJsonapiResource}}}" + ] + } +} \ No newline at end of file diff --git a/tests/testphase/userDataTemplate/get.json b/tests/testphase/userDataTemplate/get.json new file mode 100644 index 0000000..4e5526a --- /dev/null +++ b/tests/testphase/userDataTemplate/get.json @@ -0,0 +1,25 @@ +{ + "meta": { + "order": 100 + }, + "request": { + "url": "user_data_template/", + "method": "GET", + "headers": [ + "{{{headerRequestAccept}}}", + "{{{headerRequestContentType}}}" + ] + }, + "response": { + "statusCode": 200, + "headers": { + "Content-Type": "{{{headerResponseContentType}}}" + }, + "ruleObjects": [ + "{{{responseBodyJsonapiCollection}}}" + ], + "export": { + "templateId1": "data.id" + } + } +} \ No newline at end of file From 4ee2fadbd6be8a0781ca33b80dfc62c31992ee7d Mon Sep 17 00:00:00 2001 From: Kostas Nikolopoulos Date: Fri, 9 Jun 2017 19:07:13 +0300 Subject: [PATCH 2/2] Added validation from object for data_user (answer) --- src/Controllers/UserDataController.php | 33 +++++---- .../UserDataTemplateController.php | 4 +- tests/testphase/userData/post.json | 3 +- tools/database.php | 70 +++++++++++++++++++ 4 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/Controllers/UserDataController.php b/src/Controllers/UserDataController.php index 1780a21..fd98058 100644 --- a/src/Controllers/UserDataController.php +++ b/src/Controllers/UserDataController.php @@ -18,13 +18,15 @@ namespace Phramework\Examples\JSONAPI\Controllers; +use Phramework\Examples\JSONAPI\Models\UserDataTemplate; use Phramework\Examples\JSONAPI\Request; use Phramework\Examples\JSONAPI\Models\UserData; use Phramework\Phramework; +use Phramework\Validate\ObjectValidator; /** * @license https://www.apache.org/licenses/LICENSE-2.0 Apache-2.0 - * @author Xenofon Spafaridis + * @author Kostas Nikolopoulos */ class UserDataController extends \Phramework\Examples\JSONAPI\Controller { @@ -34,7 +36,7 @@ class UserDataController extends \Phramework\Examples\JSONAPI\Controller * @param string $method Request method * @param array $headers Request headers */ - public static function GET($params, $method, $headers) + public static function GET( \stdClass $params, string $method, array $headers) { $user = Request::checkPermission(); @@ -53,7 +55,7 @@ public static function GET($params, $method, $headers) * @param array $headers Request headers * @param string $id Resource id */ - public static function GETById($params, $method, $headers, string $id) + public static function GETById(\stdClass $params, string $method, array $headers, string $id) { $user = Request::checkPermission(); @@ -93,7 +95,12 @@ function ( \stdClass $attributes, \stdClass $parsedRelationshipAttributes ) use ($user) { - $attributes->user_id = $user->id; + $attributes->user_id = $user->id; // inject user id from authorization + $template = UserDataTemplate::getById($requestRelationships->template->data->id); // get template in order to create validator + $validator = new ObjectValidator(); + $templateJson = json_decode($template->attributes->value); + $validator = $validator->createFromObject($templateJson); // create validator + $attributes->value = $validator->parse($attributes->value); // validate data } ], /** @@ -111,22 +118,20 @@ function (array $ids) { } ); } - - - + /** * Manage resource's relationships * `/article/{id}/relationships/{relationship}/` handler - * @param \stdClass $params Request parameters + * @param \stdClass $params Request parameters * @param string $method Request method * @param array $headers Request headers * @param string $id Resource id * @param string $relationship Relationship */ public static function byIdRelationships( - $params, - $method, - $headers, + \stdClass $params, + string $method, + array $headers, string $id, string $relationship ) { @@ -142,10 +147,4 @@ public static function byIdRelationships( [] ); } - - - - - - } diff --git a/src/Controllers/UserDataTemplateController.php b/src/Controllers/UserDataTemplateController.php index a697c14..37ea79e 100644 --- a/src/Controllers/UserDataTemplateController.php +++ b/src/Controllers/UserDataTemplateController.php @@ -33,7 +33,7 @@ class UserDataTemplateController extends \Phramework\Examples\JSONAPI\Controller * @param string $method Request method * @param array $headers Request headers */ - public static function GET($params, $method, $headers) + public static function GET( \stdClass $params, string $method, array $headers) { static::handleGET( $params, @@ -50,7 +50,7 @@ public static function GET($params, $method, $headers) * @param array $headers Request headers * @param string $id Resource id */ - public static function GETById($params, $method, $headers, string $id) + public static function GETById( \stdClass $params, string $method, array $headers, string $id) { static::handleGETById( $params, diff --git a/tests/testphase/userData/post.json b/tests/testphase/userData/post.json index 7df157b..ae7dfbc 100644 --- a/tests/testphase/userData/post.json +++ b/tests/testphase/userData/post.json @@ -16,12 +16,11 @@ "type": "user_data", "attributes": { "value": {"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}} - }, "relationships": { "template": { "data": { - "id": "1", + "id": "{{templateId1}}", "type": "user_data_template" } } diff --git a/tools/database.php b/tools/database.php index 9a31690..f3028c5 100644 --- a/tools/database.php +++ b/tools/database.php @@ -62,6 +62,22 @@ )' ); +$adapter->execute( + 'CREATE TABLE `user_data_template`( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `value` TEXT + )' +); + +$adapter->execute( + 'CREATE TABLE `user_data`( + `id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, + `user_id` INTEGER, + `user_data_template_id` INTEGER, + `value` TEXT + )' +); + /* * Define lists of records to be inserted */ @@ -110,7 +126,37 @@ [1, 2, 1], [2, 1, 1] ]; +$user_data_templates = [ + [ + 1, + '{"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}}', + ], + [ + 2, + '{"type":"object","properties":{"platform":{"type":"enum","enum":["www","ios","android"],"title":"Your platform","description":"Tell us your platform","meta":{"display":"radio","enum-titles":{"www":"Web browser","android":"Android"}}}}}', + ], +]; +$user_data_entries = [ + [ + 1, + 1, + 1, + '{"platform":"www"}', + ], + [ + 2, + 1, + 2, + '{"platform":"ios"}', + ], + [ + 3, + 2, + 2, + '{"platform":"ios"}', + ], +]; /* * Insert user records @@ -160,6 +206,30 @@ ); } +/* + * Insert user_data_template records + */ +foreach ($user_data_templates as $user_data_template) { + $adapter->execute( + 'INSERT INTO `user_data_template` + (`id`, `value`) + VALUES (?, ?)', + $user_data_template + ); +} + +/* + * Insert user_data_template records + */ +foreach ($user_data_entries as $user_data) { + $adapter->execute( + 'INSERT INTO `user_data` + (`id`, `user_id`, `user_data_template_id`, `value`) + VALUES (?, ?, ?, ?)', + $user_data + ); +} + /* * Close connection */