mirror of
https://github.com/ConnectedHumber/Air-Quality-Web
synced 2024-12-21 10:25:00 +00:00
Implement a bunch of stuff.
Things are coming along fast!
This commit is contained in:
parent
66eeb9010d
commit
cef578d7eb
14 changed files with 293 additions and 24 deletions
3
api.php
3
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();
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
7
logic/Actions/IAction.php
Normal file
7
logic/Actions/IAction.php
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace AirQuality\Actions;
|
||||
|
||||
interface IAction {
|
||||
public function handle() : boolean;
|
||||
}
|
24
logic/ApiResponseSender.php
Normal file
24
logic/ApiResponseSender.php
Normal file
|
@ -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)
|
||||
|
|
25
logic/PerfFormatter.php
Normal file
25
logic/PerfFormatter.php
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
7
logic/Repositories/IDeviceRepository.php
Normal file
7
logic/Repositories/IDeviceRepository.php
Normal file
|
@ -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();
|
||||
}
|
||||
|
|
29
logic/Repositories/MariaDBDeviceRepository.php
Normal file
29
logic/Repositories/MariaDBDeviceRepository.php
Normal file
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
66
logic/Repositories/MariaDBMeasurementDataRepository.php
Normal file
66
logic/Repositories/MariaDBMeasurementDataRepository.php
Normal file
|
@ -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
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
39
logic/Repositories/MariaDBMeasurementTypeRepository.php
Normal file
39
logic/Repositories/MariaDBMeasurementTypeRepository.php
Normal file
|
@ -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…
Reference in a new issue