Implement a bunch of stuff.

Things are coming along fast!
pull/26/head
Starbeamrainbowlabs 4 years ago
parent 66eeb9010d
commit cef578d7eb
Signed by: sbrl
GPG Key ID: 1BE5172E637709C2
  1. 3
      api.php
  2. 12
      di_config.php
  3. 62
      logic/Actions/FetchData.php
  4. 7
      logic/Actions/IAction.php
  5. 24
      logic/ApiResponseSender.php
  6. 9
      logic/Database.php
  7. 25
      logic/PerfFormatter.php
  8. 7
      logic/Repositories/IDeviceRepository.php
  9. 2
      logic/Repositories/IMeasurementDataRepository.php
  10. 19
      logic/Repositories/IMeasurementTypeRepository.php
  11. 29
      logic/Repositories/MariaDBDeviceRepository.php
  12. 66
      logic/Repositories/MariaDBMeasurementDataRepository.php
  13. 39
      logic/Repositories/MariaDBMeasurementTypeRepository.php
  14. 13
      logic/Validator.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();

@ -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" ])
];

@ -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;
}

@ -0,0 +1,7 @@
<?php
namespace AirQuality\Actions;
interface IAction {
public function handle() : boolean;
}

@ -0,0 +1,24 @@
<?php
namespace AirQuality;
/**
* Automates the sending of API responses.
*/
class ApiResponseSender
{
function __construct() {
}
public function send_error_plain($code, $message, $extra_headers = []) {
http_response_code($code);
header("content-type: text/plain");
header("content-length: " . strlen($message)); // strlen isn't multibyte-safe, so it'll give us the actual length of the string in bytes, not characters - which is precisely what we want here :D
foreach($extra_headers as $header)
header("{$header[0]}: {$header[1]}");
echo($message);
}
}

@ -14,10 +14,10 @@ class Database
private $pdo_options = [
// https://devdocs.io/php/pdo.setattribute
PDO::ATTR_ERRMODE => 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)

@ -0,0 +1,25 @@
<?php
/**
* Formats performance data to be sent in a HTTP header.
*/
class PerfFormatter {
function __construct() {
}
public static function format_perf_data($start_init, $start_handle, $start_encode) {
$now = microtime(true);
$time_total = round($now - $start_init, 2);
$time_setup = round($start_handle - $start_init, 2);
$time_handle = round(($start_encode ?? $now) - $start_handle, 2);
$time_encode = round($now - $start_encode, 2);
$result = "{$time_total}ms total | {$time_setup}ms setup, {$time_handle}ms handle";
if(!empty($start_encode))
$result .= ", {$time_encode}ms encode";
return $result;
}
}

@ -0,0 +1,7 @@
<?php
namespace AirQuality\Repositories;
interface IDeviceRepository {
}

@ -3,5 +3,5 @@
namespace AirQuality\Repositories;
interface IMeasurementDataRepository {
public function get_readings_by_date(DateTime $datetime, string $reading_type);
public function get_readings_by_date(\DateTime $datetime, string $reading_type);
}

@ -3,6 +3,23 @@
namespace AirQuality\Repositories;
interface IMeasurementTypeRepository {
public function is_valid_type();
/**
* Returns whether the specified type is valid or not.
* @param string $type_name The name of the type to validate.
* @return boolean Whether the specified type name is valid or not.
*/
public function is_valid_type(string $type_name);
/**
* Gets the friendly name for the specified type name.
* @param string $type_name The type name to get the friendly name for.
* @return string The friendly name for the specified type name.
*/
public function get_friendly_name(string $type_name);
/**
* Returns all the currently known meeasurement types.
* @return array All the measurement types currently known.
*/
public function get_all_types();
}

@ -0,0 +1,29 @@
<?php
namespace AirQuality\Repositories;
/**
* Fetches device info from a MariaDB database.
*/
class MariaDBDeviceRepository implements IDeviceRepository {
public static $table_name = "devices";
public static $column_device_id = "device_id";
public static $column_device_name = "device_name";
public static $column_device_type = "device_type";
public static $column_owner_id = "owner_id";
public static $column_lat = "device_latitude";
public static $column_long = "device_longitude";
// ------------------------------------------------------------------------
/**
* The database connection.
* @var \AirQuality\Database
*/
private $database;
function __construct(\AirQuality\Database $in_database) {
$this->database = $in_database;
}
}

@ -0,0 +1,66 @@
<?php
namespace AirQuality\Repositories;
/**
* Fetches measurement readings from a MariaDB database.
*/
class MariaDBMeasurementDataRepository implements IMeasurementDataRepository {
public static $table_name_metadata = "readings";
public static $table_name_values = "reading_values";
public static $column_values_id = "id";
public static $column_values_reading_id = "reading_id";
public static $column_values_value = "value";
public static $column_values_reading_value_types_id = "reading_value_types_id";
public static $column_metadata_id = "id";
public static $column_metadata_storedon = "storedon";
public static $column_metadata_recordedon = "recordedon";
public static $column_metadata_device_id = "device_id";
public static $column_metadata_lat = "reading_latitude";
public static $column_metadata_long = "reading_longitude";
// ------------------------------------------------------------------------
/**
* The database connection.
* @var \AirQuality\Database
*/
private $database;
function __construct(\AirQuality\Database $in_database) {
$this->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
]
);
}
}

@ -0,0 +1,39 @@
<?php
namespace AirQuality\Repositories;
/**
* Fetches device info from a MariaDB database.
*/
class MariaDBMeasurementTypeRepository implements IMeasurementTypeRepository {
public static $table_name = "reading_value_types";
public static $column_id = "id";
public static $column_friendly_text = "friendly_text";
// ------------------------------------------------------------------------
/**
* The database connection.
* @var \AirQuality\Database
*/
private $database;
function __construct(\AirQuality\Database $in_database) {
$this->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 :-\\");
}
}

@ -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(

Loading…
Cancel
Save