[server] Add device-data-bounds API call

This commit is contained in:
Starbeamrainbowlabs 2019-01-19 16:08:47 +00:00
parent 556585e848
commit 0cefd047f4
Signed by: sbrl
GPG key ID: 1BE5172E637709C2
8 changed files with 224 additions and 3 deletions

View file

@ -93,6 +93,20 @@ _No parameters are currently supported by this action._
https://example.com/path/to/api.php?action=list-reading-types
```
### device-data-bounds
> Gets the start and end DateTime bounds for the data recorded for a specific device.
Parameter | Type | Meaning
--------------------|-----------|---------------------
`device-id` | int | Required. The id of the device to get the data DateTime bounds for.
```
https://example.com/path/to/api.php?action=device-data-bounds&device-id=18
https://example.com/path/to/api.php?action=device-data-bounds&device-id=11
```
## Notes
- Readings are taken every 6 minutes as standard.

View file

@ -0,0 +1,42 @@
"use strict";
// import CreateElement from 'dom-create-element-query-selector';
// We're using the git repo for now until an update is released, and rollup doesn't like that apparently
import CreateElement from '../../node_modules/dom-create-element-query-selector/src/index.js';
import Chart from 'chart.js';
import GetFromUrl from './Helpers/GetFromUrl.mjs';
class DeviceReadingDisplay {
get device_id() { return this._device_id; }
set device_id(value) {
this._device_id = value;
this.update_display();
}
constructor(in_config, in_device_id) {
this.setup_display();
this.config = in_config;
this._device_id = in_device_id; // We don't want to update until we have everything we need
}
setup_display() {
this.display = CreateElement("div.chart-device-data",
CreateElement("canvas.canvas-chart"),
CreateElement("ul.reading-types")
);
}
set_reading_types(new_reading_types, starting_index) {
this.reading_types = new_reading_types;
this.selected_reading_type = this.reading_types[starting_index];
}
async update_display() {
let new_data = JSON.parse(await GetFromUrl(`${this.config.api_root}?action=`))
}
}
export default DeviceReadingDisplay;

View file

@ -0,0 +1,72 @@
<?php
namespace AirQuality\Actions;
use \SBRL\TomlConfig;
use \AirQuality\Repositories\IMeasurementDataRepository;
use \AirQuality\ApiResponseSender;
use \AirQuality\Validator;
use \AirQuality\PerfFormatter;
class DeviceDataBounds implements IAction {
/** @var TomlConfig */
private $settings;
/** @var IMeasurementDataRepository */
private $measurement_repo;
/** @var ApiResponseSender */
private $sender;
/** @var Validator */
private $validator;
public function __construct(
TomlConfig $in_settings,
IMeasurementDataRepository $in_measurement_repo,
ApiResponseSender $in_sender) {
$this->settings = $in_settings;
$this->measurement_repo = $in_measurement_repo;
$this->sender = $in_sender;
$this->validator = new Validator($_GET);
}
public function handle() : bool {
global $start_time;
$start_handle = microtime(true);
// 1: Validate params
$this->validator->is_numberish("device-id");
$this->validator->run();
// 2: Pull data from database
$data = $this->measurement_repo->get_device_reading_bounds(
intval($_GET["device-id"])
);
// 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 has been recorded from the device id or it doesn't exist.");
return false;
}
// 3: Serialise data
$start_encode = microtime(true);
$response = json_encode($data);
// 4: Send response
header("content-length: " . strlen($response));
header("content-type: application/json");
header("x-time-taken: " . PerfFormatter::format_perf_data($start_time, $start_handle, $start_encode));
echo($response);
return true;
}
}

View file

@ -42,6 +42,7 @@ class Database
}
public function query($sql, $variables = []) {
error_log("[Database/SQL] $sql");
// FUTURE: Optionally cache prepared statements?
$statement = $this->connection->prepare($sql);
$statement->execute($variables);

View file

@ -3,5 +3,29 @@
namespace AirQuality\Repositories;
interface IMeasurementDataRepository {
public function get_readings_by_date(\DateTime $datetime, string $reading_type);
/**
* Returns the specified reading type for all devices at the specified date
* and time.
* @param DateTime $datetime The date and time to get the readings for.
* @param string $reading_type The reading type to fetch.
* @return array The requested readings.
*/
public function get_readings_by_date(\DateTime $datetime, string $reading_type);
/**
* Gets the first and last DateTimes of the readings for a specified device.
* @param int $device_id The device id to fetch for.
* @return DateTime[] The first and last DateTimes for which readings are stored.
*/
public function get_device_reading_bounds(int $device_id);
/**
* Gets the readings of a specified type for a specific device between the 2 given dates and times.
* @param int $device_id The id of the device to fetch data for.
* @param string $reading_type The reading type to fetch.
* @param DateTime $start The starting DateTime.
* @param DateTime $end The ending DateTime.
* @return array The requested data.
*/
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end);
}

View file

@ -93,4 +93,27 @@ class MariaDBMeasurementDataRepository implements IMeasurementDataRepository {
]
)->fetchAll();
}
public function get_device_reading_bounds(int $device_id) {
$s = $this->get_static;
return $this->database->query(
"SELECT
MIN(COALESCE(
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
)) AS start,
MAX(COALESCE(
{$s("table_name_metadata")}.{$s("column_metadata_recordedon")},
{$s("table_name_metadata")}.{$s("column_metadata_storedon")}
)) AS end
FROM {$s("table_name_metadata")}
WHERE {$s("table_name_metadata")}.{$s("column_metadata_device_id")} = :device_id;", [
$device_id
]
)->fetch();
}
public function get_readings_by_device(int $device_id, string $reading_type, \DateTime $start, \DateTime $end) {
}
}

47
package-lock.json generated
View file

@ -4,6 +4,12 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@types/chart.js": {
"version": "2.7.42",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.7.42.tgz",
"integrity": "sha512-+0v+PV2J9wsV7u36Vqt5Oke7ugtiZqNeYJgNk5mgNMkEkqtjxo2Q9N5Q9znHlgmXeOTfQL6e1sfcAbTnPHAzlA==",
"dev": true
},
"@types/estree": {
"version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
@ -247,6 +253,39 @@
"supports-color": "^5.3.0"
}
},
"chart.js": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.3.tgz",
"integrity": "sha512-3+7k/DbR92m6BsMUYP6M0dMsMVZpMnwkUyNSAbqolHKsbIzH2Q4LWVEHHYq7v0fmEV8whXE0DrjANulw9j2K5g==",
"requires": {
"chartjs-color": "^2.1.0",
"moment": "^2.10.2"
}
},
"chartjs-color": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/chartjs-color/-/chartjs-color-2.2.0.tgz",
"integrity": "sha1-hKL7dVeH7YXDndbdjHsdiEKbrq4=",
"requires": {
"chartjs-color-string": "^0.5.0",
"color-convert": "^0.5.3"
},
"dependencies": {
"color-convert": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-0.5.3.tgz",
"integrity": "sha1-vbbGnOZg+t/+CwAHzER+G59ygr0="
}
}
},
"chartjs-color-string": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz",
"integrity": "sha512-amWNvCOXlOUYxZVDSa0YOab5K/lmEhbFNKI55PWc4mlv28BDzA7zaoQTGxSBgJMHIW+hGX8YUrvw/FH4LyhwSQ==",
"requires": {
"color-name": "^1.0.0"
}
},
"clap": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz",
@ -321,8 +360,7 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "0.3.0",
@ -1193,6 +1231,11 @@
"minimist": "0.0.8"
}
},
"moment": {
"version": "2.23.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz",
"integrity": "sha512-3IE39bHVqFbWWaPOMHZF98Q9c3LDKGTmypMiTM2QygGXXElkFWIH7GxfmlwmY2vwa+wmNsoYZmG2iusf1ZjJoA=="
},
"normalize-path": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz",

View file

@ -17,6 +17,7 @@
},
"homepage": "https://github.com/sbrl/ConnectedHumber-Air-Quality-Interface#readme",
"dependencies": {
"chart.js": "^2.7.3",
"dom-create-element-query-selector": "github:hekigan/dom-create-element-query-selector",
"leaflet": "^1.4.0",
"leaflet-fullscreen": "^1.0.2",
@ -25,6 +26,7 @@
"smartsettings": "^1.2.3"
},
"devDependencies": {
"@types/chart.js": "^2.7.42",
"@types/leaflet": "^1.2.14",
"@types/leaflet-fullscreen": "^1.0.4",
"leaflet-heatmap": "^1.0.0",