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