From 342e9f7c16f511be873307fcc83cc5c92653b328 Mon Sep 17 00:00:00 2001 From: Artem Konenko <yadummer@gmail.com> Date: Wed, 10 Aug 2016 12:32:42 +0300 Subject: [PATCH] #119 Add teacher insertion to API * Add getDepartmentIdByName API (with auxiliary procedure) * Add teacher insertion (only one at the time) * Add token checking to user authorization in API handler * Add throwing away HTTP exceptions in API handler * Add functions for throwing HTTP exceptions (403, 404) * Add singing in by tokens in User * Add autocompleting department's ID by name * Add error handling (show 400 bad request, if any require data missed) * Add batch processing * Add autocompleting job position ID by name --- Makefile | 1 + db/migrations/R__api's_procedures.sql | 25 +++++ ~dev_rating/application/bootstrap.php | 1 + .../classes/Controller/Api/V0/Department.php | 18 ++++ .../classes/Controller/Api/V0/Student.php | 20 ++++ .../classes/Controller/Api/V0/Subject.php | 20 ++++ .../classes/Controller/Api/V0/Teacher.php | 67 ++++++++++++++ .../classes/Controller/Handler/Api.php | 42 ++++++++- ~dev_rating/application/classes/Error.php | 1 + .../application/classes/Model/Faculties.php | 24 +++++ ~dev_rating/application/classes/User.php | 5 + ~dev_rating/application/routes/api/v0.php | 92 +++++++++++++++++++ .../system/classes/HTTP/API/Exception/403.php | 5 + .../system/classes/HTTP/API/Exception/404.php | 5 + 14 files changed, 321 insertions(+), 5 deletions(-) create mode 100644 db/migrations/R__api's_procedures.sql create mode 100644 ~dev_rating/application/classes/Controller/Api/V0/Department.php create mode 100644 ~dev_rating/application/classes/Controller/Api/V0/Student.php create mode 100644 ~dev_rating/application/classes/Controller/Api/V0/Subject.php create mode 100644 ~dev_rating/application/classes/Controller/Api/V0/Teacher.php create mode 100644 ~dev_rating/application/routes/api/v0.php create mode 100644 ~dev_rating/system/classes/HTTP/API/Exception/403.php create mode 100644 ~dev_rating/system/classes/HTTP/API/Exception/404.php diff --git a/Makefile b/Makefile index 51424f906..0f5de56e3 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,7 @@ copy_files: Tasker_deploy \~dev_rating/ $(DST_PATH) # server deploy without db fix and stored stuff update +# ToDo: make like sed -i 's/~dev_rating/$target/g' $(DST_PATH)/.htaccess release_no_db: copy_files sed -i 's/~dev_rating//g' $(DST_PATH)/.htaccess sed -i 's/\/~dev_rating//g' $(DST_PATH)/application/bootstrap.php diff --git a/db/migrations/R__api's_procedures.sql b/db/migrations/R__api's_procedures.sql new file mode 100644 index 000000000..95fcda9e7 --- /dev/null +++ b/db/migrations/R__api's_procedures.sql @@ -0,0 +1,25 @@ +START TRANSACTION; + +DELIMITER // + +DROP PROCEDURE IF EXISTS GetDepartmentIdByName// +CREATE PROCEDURE GetDepartmentIdByName( + IN pDepartmentName varchar(200)) + BEGIN + SELECT departments.ID as ID + FROM departments + WHERE departments.Name = pDepartmentName; + END// + +DROP PROCEDURE IF EXISTS GetJobPositionIdByName// +CREATE PROCEDURE GetJobPositionIdByName( + IN pJobPositionName varchar(200)) + BEGIN + SELECT job_positions.ID as ID + FROM job_positions + WHERE job_positions.Name = pJobPositionName; + END// + +DELIMITER ; + +COMMIT ; diff --git a/~dev_rating/application/bootstrap.php b/~dev_rating/application/bootstrap.php index 252f9d6cc..32df89579 100644 --- a/~dev_rating/application/bootstrap.php +++ b/~dev_rating/application/bootstrap.php @@ -140,6 +140,7 @@ Kohana::modules([ // dedicated routes require APPPATH . 'routes/api.php'; +require APPPATH . 'routes/api/v0.php'; require APPPATH . 'routes/dean_office.php'; diff --git a/~dev_rating/application/classes/Controller/Api/V0/Department.php b/~dev_rating/application/classes/Controller/Api/V0/Department.php new file mode 100644 index 000000000..158588bcb --- /dev/null +++ b/~dev_rating/application/classes/Controller/Api/V0/Department.php @@ -0,0 +1,18 @@ +<?php + +class Controller_Api_V0_Department extends Controller_Handler_Api +{ + public function action_get_idByName() { + $name = $this->request->query('name'); + + try { + $id = Model_Faculties::getDepartmentIdByName($name); + } + catch (InvalidArgumentException $e) { + $this->notFoundError($e->getMessage()); + } + + return $id; + } + +} diff --git a/~dev_rating/application/classes/Controller/Api/V0/Student.php b/~dev_rating/application/classes/Controller/Api/V0/Student.php new file mode 100644 index 000000000..6a984ecea --- /dev/null +++ b/~dev_rating/application/classes/Controller/Api/V0/Student.php @@ -0,0 +1,20 @@ +<?php + +class Controller_Api_V0_Student extends Controller_Handler_Api +{ + public function action_get_index() { + $this->response->body( '{ what: get index }' ); + + return '{ what: get index }'; //$this->response; + } + + public function action_put_index() { + $firstName = $this->request->query('firstName'); + + return '{ what: put index, id: '.$firstName.' }'; + } + + public function action_get_list_by_name() { + return '{ what: nothing }'; + } +} diff --git a/~dev_rating/application/classes/Controller/Api/V0/Subject.php b/~dev_rating/application/classes/Controller/Api/V0/Subject.php new file mode 100644 index 000000000..7aeb6fcb0 --- /dev/null +++ b/~dev_rating/application/classes/Controller/Api/V0/Subject.php @@ -0,0 +1,20 @@ +<?php + +class Controller_Api_V0_Subject extends Controller_Handler_Api +{ + public function action_get_index() { + return '{ name: "Русский язык", + grade: 1, + group: 2 }'; + } + + public function action_put_index() { + $firstName = $this->request->query('firstName'); + + return '{ what: put index, id: '.$firstName.' }'; + } + + public function action_get_list_by_name() { + return '{ what: nothing }'; + } +} diff --git a/~dev_rating/application/classes/Controller/Api/V0/Teacher.php b/~dev_rating/application/classes/Controller/Api/V0/Teacher.php new file mode 100644 index 000000000..be9ee5033 --- /dev/null +++ b/~dev_rating/application/classes/Controller/Api/V0/Teacher.php @@ -0,0 +1,67 @@ +<?php + +class Controller_Api_V0_Teacher extends Controller_Handler_Api +{ + // ToDo: The functions is copy-paste from departments API. Should be extracted. + private function getDepIDByName($name) { + try { + $id = Model_Faculties::getDepartmentIdByName($name); + } + catch (InvalidArgumentException $e) { + return NULL; + } + return $id; + } + + private function createTeacher($data) { + $teacher = Model_Teacher::make() + ->jobPosition($data->jobPositionID) + ->department($data->departmentID) + ->firstName($data->firstName) + ->secondName($data->secondName) + ->lastName($data->lastName) + ->create(); + return ['ID' => $teacher->ID, 'Code' => $teacher->ActivationCode]; + } + + private function normolizeTeacherData($data) { + if ( $data->departmentID === NULL && $data->departmentName !== NULL) + $data->departmentID = $this->getDepIDByName($data->departmentName); + + if ( $data->jobPositionID === NULL && $data->jobPositionName !== NULL ) + $data->jobPositionID = Model_Faculties::getJobPositionIdByName($data->jobPositionName); + + return $data; + } + + public function action_put_index() { + try { + if ( $this->request->query('batch') !== NULL ) { + $data = json_decode($this->request->body()); + + $res = []; + foreach ($data as $item) { + $data = $this->normolizeTeacherData($item); + $res[] = $this->createTeacher($data); + } + } else { + $data = [ 'firstName' => $this->request->query('firstName') + , 'secondName' => $this->request->query('secondName') + , 'lastName' => $this->request->query('lastName') + , 'departmentName' => $this->request->query('departmentName') + , 'departmentID' => $this->request->query('departmentID') + , 'jobPositionName' => $this->request->query('jobPositionName') + , 'jobPositionID' => $this->request->query('jobPositionID') + , 'status' => $this->request->query('status')]; + + $data = $this->normolizeTeacherData((object)$data); + $res[] = $this->createTeacher($data); + } + } catch (Exception $e) { + $this->badRequestError($e->getMessage()); + } + + return $res; + } + +} diff --git a/~dev_rating/application/classes/Controller/Handler/Api.php b/~dev_rating/application/classes/Controller/Handler/Api.php index b03aa2cda..c0e8781c9 100644 --- a/~dev_rating/application/classes/Controller/Handler/Api.php +++ b/~dev_rating/application/classes/Controller/Handler/Api.php @@ -1,9 +1,5 @@ <?php defined('SYSPATH') OR die('No direct script access.'); -/** - * Class Controller_Handler_Api - * Not stable, use it only if you are Igor. - */ abstract class Controller_Handler_Api extends Controller { /** @var array */ @@ -15,13 +11,26 @@ abstract class Controller_Handler_Api extends Controller /** @var User */ protected $user; + /** @var Token */ + protected $token; + public function before() { // todo: var $user should be defined here // either by GET param (token), // or on the session variables. + $this->token = $this->request->query('token'); + if ( $this->token != '' ) + { + $ok = User::instance()->signInByToken($this->token); + if (!$ok) $this->fail(); + } + $this->user = User::instance(); + if (!$this->user->isAuthorized()) + $this->fail(); + $this->get =& $_GET; $this->post =& $_POST; } @@ -35,7 +44,7 @@ abstract class Controller_Handler_Api extends Controller // If the action doesn't exist, it's a 404 if (!method_exists($this, $action)) { - throw HTTP_Exception::factory(404, + throw HTTP_API_Exception::factory(404, 'The requested URL :uri was not found on this server.', [':uri' => $this->request->uri()] )->request($this->request); @@ -46,6 +55,8 @@ abstract class Controller_Handler_Api extends Controller $data = $this->{$action}(); $answer = ['response' => $data ?: '']; $json_answer = json_encode($answer, JSON_UNESCAPED_UNICODE); + } catch (HTTP_API_Exception $e) { // Let HTTP errors go throw away + throw $e; } catch (Exception $e) { // todo: improve error capturing while ($e->getPrevious()) @@ -126,4 +137,25 @@ abstract class Controller_Handler_Api extends Controller return $result; } + + private function fail() { + throw HTTP_API_Exception::factory(403, + 'Token is broken.', + [':uri' => $this->request->uri()] + )->request($this->request); + } + + protected function notFoundError($message) { + throw HTTP_API_Exception::factory(404, + $message, + [':uri' => $this->request->uri()] + )->request($this->request); + } + + protected function badRequestError($message) { + throw HTTP_API_Exception::factory(400, + $message, + [':uri' => $this->request->uri()] + )->request($this->request); + } } \ No newline at end of file diff --git a/~dev_rating/application/classes/Error.php b/~dev_rating/application/classes/Error.php index 42f14b6c2..c9202c8d1 100644 --- a/~dev_rating/application/classes/Error.php +++ b/~dev_rating/application/classes/Error.php @@ -6,4 +6,5 @@ class Error { const DISCIPLINE_IS_LOCKED = 'Discipline is locked'; const DISCIPLINE_NOT_FOUND = 'Discipline does not exist'; const INVALID_PARAMETERS = 'Invalid parameters'; + const DEPARTMENT_NOT_FOUND = 'Department does not exist'; } diff --git a/~dev_rating/application/classes/Model/Faculties.php b/~dev_rating/application/classes/Model/Faculties.php index 00bf19d90..9f6898ec4 100644 --- a/~dev_rating/application/classes/Model/Faculties.php +++ b/~dev_rating/application/classes/Model/Faculties.php @@ -25,4 +25,28 @@ class Model_Faculties extends Model return $list; } + + public static function getDepartmentIdByName($departmentName) { + $sql = 'CALL `GetDepartmentIdByName`(:name)'; + $id = DB::query(Database::SELECT, $sql) + ->param(':name', $departmentName) + ->execute(); + + if ($id->count() == 0) + throw new InvalidArgumentException(Error::DEPARTMENT_NOT_FOUND); + + return $id->get('ID'); + } + + public static function getJobPositionIdByName($jobPositionName) { + $sql = 'CALL `GetJobPositionIdByName`(:name)'; + $id = DB::query(Database::SELECT, $sql) + ->param(':name', $jobPositionName) + ->execute(); + + if ($id->count() == 0) + return NULL; + + return $id->get('ID'); + } } diff --git a/~dev_rating/application/classes/User.php b/~dev_rating/application/classes/User.php index 67cf33c39..f649f47c8 100644 --- a/~dev_rating/application/classes/User.php +++ b/~dev_rating/application/classes/User.php @@ -158,6 +158,11 @@ class User implements ArrayAccess return $this->initSession($id, $this->hash($password)); } + public function signInByToken($token) { + $id = (int) Model_Account::checkAuthToken($token); + return $this->initSession($id, $this->hash($token)); + } + protected function initSession($id, $passHash) { if ($id <= 0) return false; diff --git a/~dev_rating/application/routes/api/v0.php b/~dev_rating/application/routes/api/v0.php new file mode 100644 index 000000000..285d27c6d --- /dev/null +++ b/~dev_rating/application/routes/api/v0.php @@ -0,0 +1,92 @@ +<?php +Route::set('apiv0:teacher', 'api/v0/teacher') + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Teacher', + ]); + +Route::set('apiv0:department', 'api/v0/department') + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Department', + ]); + +Route::set('apiv0:department:another', 'api/v0/department(/<action>(/<id>))', ['id' => '\d+']) + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Department', + ]); + +Route::set('apiv0:student', 'api/v0/student') + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Student', + ]); + +Route::set('apiv0:student:get', 'api/v0/student(/<id>)', ['id' => '\d+']) + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Student', + ]); + +Route::set('apiv0:student:another', 'api/v0/student(/<action>(/<id>))', ['id' => '\d+']) + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Student', + ]); + + +Route::set('apiv0:subject', 'api/v0/subject') + ->filter(function($route, $params, $request) + { + // Prefix the method to the action name + $params['action'] = strtolower($request->method()).'_'.$params['action']; + return $params; // Returning an array will replace the parameters + }) + ->defaults([ + 'action' => 'index', + 'directory' => 'Api/V0', + 'controller' => 'Subject', + ]); diff --git a/~dev_rating/system/classes/HTTP/API/Exception/403.php b/~dev_rating/system/classes/HTTP/API/Exception/403.php new file mode 100644 index 000000000..93bd98d72 --- /dev/null +++ b/~dev_rating/system/classes/HTTP/API/Exception/403.php @@ -0,0 +1,5 @@ +<?php defined('SYSPATH') OR die('No direct script access.'); + +class HTTP_API_Exception_403 extends HTTP_API_Exception { + protected $_code = 403; +} diff --git a/~dev_rating/system/classes/HTTP/API/Exception/404.php b/~dev_rating/system/classes/HTTP/API/Exception/404.php new file mode 100644 index 000000000..2cd10fe14 --- /dev/null +++ b/~dev_rating/system/classes/HTTP/API/Exception/404.php @@ -0,0 +1,5 @@ +<?php defined('SYSPATH') OR die('No direct script access.'); + +class HTTP_API_Exception_404 extends HTTP_API_Exception { + protected $_code = 404; +} -- GitLab