From cef578d7eb331997c2708d9aa42e31abb6d960ca Mon Sep 17 00:00:00 2001 From: Starbeamrainbowlabs Date: Tue, 15 Jan 2019 15:46:24 +0000 Subject: [PATCH] Implement a bunch of stuff. Things are coming along fast! --- api.php | 3 + di_config.php | 12 ++++ logic/Actions/FetchData.php | 62 +++++++++++++++-- logic/Actions/IAction.php | 7 ++ logic/ApiResponseSender.php | 24 +++++++ logic/Database.php | 9 +-- logic/PerfFormatter.php | 25 +++++++ logic/Repositories/IDeviceRepository.php | 7 ++ .../IMeasurementDataRepository.php | 2 +- .../IMeasurementTypeRepository.php | 19 +++++- .../Repositories/MariaDBDeviceRepository.php | 29 ++++++++ .../MariaDBMeasurementDataRepository.php | 66 +++++++++++++++++++ .../MariaDBMeasurementTypeRepository.php | 39 +++++++++++ logic/Validator.php | 13 +--- 14 files changed, 293 insertions(+), 24 deletions(-) create mode 100644 logic/Actions/IAction.php create mode 100644 logic/ApiResponseSender.php create mode 100644 logic/PerfFormatter.php create mode 100644 logic/Repositories/IDeviceRepository.php create mode 100644 logic/Repositories/MariaDBDeviceRepository.php create mode 100644 logic/Repositories/MariaDBMeasurementDataRepository.php create mode 100644 logic/Repositories/MariaDBMeasurementTypeRepository.php diff --git a/api.php b/api.php index 3f2dd2c..8b6ac7e 100644 --- a/api.php +++ b/api.php @@ -17,6 +17,9 @@ $autoloader->register(); $di_builder = new DI\ContainerBuilder(); $di_builder->addDefinitions("di_config.php"); +// TODO: In production, we should use something like this - see http://php-di.org/doc/container-configuration.html +// $builder->enableCompilation(__DIR__ . '/tmp'); +// $builder->writeProxiesToFile(true, __DIR__ . '/tmp/proxies'); $di_container = $di_builder->build(); diff --git a/di_config.php b/di_config.php index ab7d289..755b505 100644 --- a/di_config.php +++ b/di_config.php @@ -3,6 +3,13 @@ use Psr\Container\ContainerInterface; use SBRL\TomlConfig; +use AirQuality\Repositories\IDeviceRepository; +use AirQuality\Repositories\MariaDBDeviceRepository; +use AirQuality\Repositories\IMeasurementDataRepository; +use AirQuality\Repositories\MariaDBMeasurementDataRepository; +use AirQuality\Repositories\IMeasurementTypeRepository; +use AirQuality\Repositories\MariaDBMeasurementTypeRepository; + return [ "settings.file_default" => "data/settings.toml", "settings.file_custom" => "settings.default.toml", @@ -14,5 +21,10 @@ return [ ); }, + IDeviceRepository::class => DI\autowire(MariaDBDeviceRepository::class), + IMeasurementDataRepository::class => DI\autowire(MariaDBMeasurementDataRepository::class), + IMeasurementTypeRepository::class => DI\autowire(MariaDBMeasurementTypeRepository::class), + + \SBRL\SessionManager::class => DI\factory([ \SBRL\SessionManager::class, "get_instance" ]) ]; diff --git a/logic/Actions/FetchData.php b/logic/Actions/FetchData.php index 02a49a5..9dce0cb 100644 --- a/logic/Actions/FetchData.php +++ b/logic/Actions/FetchData.php @@ -2,34 +2,84 @@ namespace AirQuality\Actions; +use \SBRL\TomlConfig; +use \AirQuality\Repositories\IMeasurementDataRepository; +use \AirQuality\Repositories\IMeasurementTypeRepository; +use \AirQuality\ApiResponseSender; -class FetchData { +use \AirQuality\Validator; +use \AirQuality\PerfFormatter; + +class FetchData implements IAction { + /** @var TomlConfig */ private $settings; + /** @var IMeasurementDataRepository */ + private $measurement_repo; + + /** @var IMeasurementTypeRepository */ + private $type_repo; + + /** @var ApiResponseSender */ + private $sender; + + /** @var Validator */ private $validator; public function __construct( - \SBRL\TomlConfig $in_settings) { + TomlConfig $in_settings, + IMeasurementDataRepository $in_measurement_repo, + IMeasurementTypeRepository $in_type_repo, + ApiResponseSender $in_sender) { $this->settings = $in_settings; - $this->validator = new \AirQuality\Validator($_GET); + $this->measurement_repo = $in_measurement_repo; + $this->type_repo = $in_type_repo; + $this->sender = $in_sender; + + $this->validator = new Validator($_GET); } - public function handle() : Boolean { + public function handle() : boolean { global $start_time; + $start_handle = microtime(true); + // 1: Validate params $this->validator->is_datetime("datetime"); + $this->validator->exists("reading_type"); + $this->validator->is_max_length("reading_type", 256); $this->validator->run(); + if(!$this->type_repo->is_valid_type($_GET["reading_type"])) { + $this->sender->send_error_plain( + 400, "Error: That reading type is invalid.", [ + [ "x-time-taken", PerfFormatter::format_perf_data($start_time, $start_handle, null) ] + ] + ); + return false; + } + // 2: Pull data from database + $data = $this->measurement_repo->get_readings_by_date( + new \DateTime($_GET["datetime"]), + $_GET["reading_type"] + ); // 2.5: Validate data from database + if(empty($data)) { + http_response_code(404); + header("content-type: text/plain"); + header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, null)); + echo("Error: No data could be found for that timestamp."); + return false; + } // 3: Serialise data - $response = json_encode("Coming soon"); + $start_encode = microtime(true); + $response = json_encode($data); // 4: Send response - header("x-time-taken: " . (microtime(true) - $start_time) . "ms"); header("content-type: application/json"); + header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode)); echo($response); return true; } diff --git a/logic/Actions/IAction.php b/logic/Actions/IAction.php new file mode 100644 index 0000000..f8f6f8d --- /dev/null +++ b/logic/Actions/IAction.php @@ -0,0 +1,7 @@ + PDO::ERRMODE_EXCEPTION, - PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, - PDO::ATTR_EMULATE_PREPARES => false, - PDO::ATTR_AUTOCOMMIT => false + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_EMULATE_PREPARES => false, + \PDO::ATTR_AUTOCOMMIT => false ]; function __construct(\SBRL\TomlConfig $in_settings) { @@ -29,6 +29,7 @@ class Database $this->get_connection_string(), $this->settings->get("database.username"), $this->settings->get("database.password"), + $this->pdo_options ); // Make this connection read-only if(self::read_only) diff --git a/logic/PerfFormatter.php b/logic/PerfFormatter.php new file mode 100644 index 0000000..9ec313b --- /dev/null +++ b/logic/PerfFormatter.php @@ -0,0 +1,25 @@ +database = $in_database; + } + +} diff --git a/logic/Repositories/MariaDBMeasurementDataRepository.php b/logic/Repositories/MariaDBMeasurementDataRepository.php new file mode 100644 index 0000000..93ef981 --- /dev/null +++ b/logic/Repositories/MariaDBMeasurementDataRepository.php @@ -0,0 +1,66 @@ +database = $in_database; + } + + public function get_readings_by_date(\DateTime $datetime, string $reading_type) { + return $this->database->query( + "SELECT + $this->table_name_values.*, + $this->table_name_metadata.device_id, + COALESCE( + $this->table_name_metadata.$this->column_metadata_recordedon, + $this->table_name_metadata.$this->column_metadata_storedon + ) AS datetime, + COALESCE( + $this->table_name_metadata.$this->column_metadata_lat, + {MariaDBDeviceRepository::$table_name}.device_latitude + ) AS latitude, + COALESCE( + $this->table_name_metadata.$this->column_metadata_long, + devices.device_longitude + ) AS longitude + FROM $this->table_name_values + JOIN $this->table_name_metadata ON $this->table_name_values.$this->column_values_reading_id = $this->table_name_metadata.id + JOIN devices ON $this->table_name_metadata.$this->column_metadata_device_id = devices.device_id + WHERE COALESCE( + $this->table_name_metadata.$this->column_metadata_recordedon, + $this->table_name_metadata.$this->column_metadata_storedon + ) = :datetime", + [ + "datetime" => $datetime, + "reading_type" => $reading_type + ] + ); + } +} diff --git a/logic/Repositories/MariaDBMeasurementTypeRepository.php b/logic/Repositories/MariaDBMeasurementTypeRepository.php new file mode 100644 index 0000000..6c16e06 --- /dev/null +++ b/logic/Repositories/MariaDBMeasurementTypeRepository.php @@ -0,0 +1,39 @@ +database = $in_database; + } + + + + public function is_valid_type(string $type_name) { + throw new Exception("Error: Not implemented yet :-\\"); + } + public function get_friendly_name(string $type_name) { + // TODO: Cache results here? Maybe https://packagist.org/packages/thumbtack/querycache will be of some use + throw new Exception("Error: Not implemented yet :-\\"); + } + + public function get_all_types() { + throw new Exception("Error: Not implemented yet :-\\"); + } +} diff --git a/logic/Validator.php b/logic/Validator.php index 108c408..003c5f6 100644 --- a/logic/Validator.php +++ b/logic/Validator.php @@ -65,18 +65,7 @@ class Validator { $message ); } - - public function is_valid_email($key) { - $this->add_test( - $key, - function($data) { - $validator = new \EmailValidator\Validator(); - return $validator->isValid($data); - }, - 400, - "That email address isn't valid." - ); - } + public function is_datetime($key) { $this->add_test(