From f0781948f39b884bc170ef35d0eeac59d36680d5 Mon Sep 17 00:00:00 2001
From: Starbeamrainbowlabs <sbrl@starbeamrainbowlabs.com>
Date: Thu, 17 Jan 2019 15:39:50 +0000
Subject: [PATCH] [server] Add list-devices API action

---
 README.md                                     | 10 ++-
 logic/Actions/ListDevices.php                 | 65 +++++++++++++++++++
 logic/Repositories/IDeviceRepository.php      |  8 ++-
 .../Repositories/MariaDBDeviceRepository.php  | 25 +++++++
 .../MariaDBMeasurementDataRepository.php      |  1 -
 5 files changed, 105 insertions(+), 4 deletions(-)
 create mode 100644 logic/Actions/ListDevices.php

diff --git a/README.md b/README.md
index ea72c0c..b242a02 100644
--- a/README.md
+++ b/README.md
@@ -4,11 +4,13 @@
 
 This project contains the web interface for the ConnectedHumber air Quality Monitoring system. It is composed of 2 parts:
 
- - A PHP-based JSON API server (entry point: api.php)
+ - A PHP-based JSON API server (entry point: api.php) that's backed by a MariaDB server
  - A Javascript client application that runs in the browser
 
 The client-side browser application is powered by [Leaflet](https://leafletjs.com/).
 
+Note that this project is _not_ responsible for entering data into the database. This project's purpose is simply to display the data.
+
 ## System Requirements
 In order to run this program, you'll need the following:
 
@@ -17,6 +19,7 @@ In order to run this program, you'll need the following:
  - [composer](https://getcomposer.org/) - For the server-side packages
  - [Node.JS](https://nodejs.org/)
  - [npm](https://npmjs.org/) - comes with Node.JS - used for building the client-side code
+ - A [MariaDB](https://mariadb.com/) server with a database already setup with the schema data in it. Please get in contact with [ConnectedHumber](https://connectedhumber.org/) for information about the database schema and structure.
 
 ## Getting Started
 The client-side code requires building. Currently, no pre-built versions are available (though these can be provided upon request), so this must be done from source. A build script is available, however, which automates the process - as explained below.
@@ -61,12 +64,15 @@ https://example.com/path/to/api.php?action=fetch-data&datetime=2019-01-03%2007:5
 ### list-devices
 > Fetches a list of devices currently in the system.
 
-_No additional parameters are supported by this action._
+Parameter			| Type		| Meaning
+--------------------|-----------|---------------------
+`only_with_location`| bool		| Optional. If present only devices with a defined location will be returned. Useful for getting a list of devices to place on a map.
 
 Examples:
 
 ```
 https://example.com/path/to/api.php?action=list-devices
+https://example.com/path/to/api.php?action=list-devices&only-with-location=yes
 ```
 
 ## Notes
diff --git a/logic/Actions/ListDevices.php b/logic/Actions/ListDevices.php
new file mode 100644
index 0000000..e5a3efa
--- /dev/null
+++ b/logic/Actions/ListDevices.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace AirQuality\Actions;
+
+use \SBRL\TomlConfig;
+use \AirQuality\Repositories\IDeviceRepository;
+use \AirQuality\ApiResponseSender;
+
+use \AirQuality\Validator;
+use \AirQuality\PerfFormatter;
+
+class ListDevices implements IAction {
+	/** @var TomlConfig */
+	private $settings;
+	/** @var IDeviceRepository */
+	private $device_repo;
+	
+	/** @var ApiResponseSender */
+	private $sender;
+	
+	public function __construct(
+		TomlConfig $in_settings,
+		IDeviceRepository $in_device_repo,
+		ApiResponseSender $in_sender) {
+		$this->settings = $in_settings;
+		$this->device_repo = $in_device_repo;
+		$this->sender = $in_sender;
+	}
+	
+	public function handle() : bool {
+		global $start_time;
+		
+		$start_handle = microtime(true);
+		
+		// 1: Parse & validate parameters
+		$only_with_location = !empty($_GET["only_with_location"]);
+		
+		// 1: Pull data from database
+		$data = $this->device_repo->get_all_devices($only_with_location);
+		
+		// 1.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 devices are currently present in the system.");
+			return false;
+		}
+		
+		// 3: Serialise data
+		$start_encode = microtime(true);
+		$response = json_encode($data);
+		
+		// 4: Send response
+		
+		// Don't a cache control header, because new devices might get added at any time
+		// TODO: Investigate adding a short-term (~10mins?) cache-control header here
+		
+		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;
+	}
+}
diff --git a/logic/Repositories/IDeviceRepository.php b/logic/Repositories/IDeviceRepository.php
index b0942a8..a798438 100644
--- a/logic/Repositories/IDeviceRepository.php
+++ b/logic/Repositories/IDeviceRepository.php
@@ -3,5 +3,11 @@
 namespace AirQuality\Repositories;
 
 interface IDeviceRepository {
-    
+	/**
+	 * Returns an array of all the devices in the system with basic information
+	 * about each.
+	 * @param   bool    $only_with_location Whether only devices with a defined location should be returned.
+	 * @return  array	A list of devices and their basic information.
+	 */
+	public function get_all_devices($only_with_location);
 }
diff --git a/logic/Repositories/MariaDBDeviceRepository.php b/logic/Repositories/MariaDBDeviceRepository.php
index 3b887b0..f0d3c78 100644
--- a/logic/Repositories/MariaDBDeviceRepository.php
+++ b/logic/Repositories/MariaDBDeviceRepository.php
@@ -22,8 +22,33 @@ class MariaDBDeviceRepository implements IDeviceRepository {
 	 */
 	private $database;
 	
+	/** Function that gets a static variable by it's name. Useful in preparing SQL queries. */
+	private $get_static;
+	
 	function __construct(\AirQuality\Database $in_database) {
 		$this->database = $in_database;
+		
+		$this->get_static = function($name) { return self::$$name; };
 	}
 	
+	
+	public function get_all_devices($only_with_location) {
+		$s = $this->get_static;
+		
+		$sql = "SELECT
+			{$s("column_device_id")},
+			{$s("column_device_name")},
+			{$s("column_lat")},
+			{$s("column_long")}
+		FROM {$s("table_name")}";
+		
+		if($only_with_location)
+			$sql .= "\nWHERE
+				{$s("column_lat")} IS NOT NULL
+				AND {$s("column_long")} IS NOT NULL";
+		
+		$sql .= ";";
+		
+		return $this->database->query($sql)->fetchAll();
+	}
 }
diff --git a/logic/Repositories/MariaDBMeasurementDataRepository.php b/logic/Repositories/MariaDBMeasurementDataRepository.php
index d316023..3a83496 100644
--- a/logic/Repositories/MariaDBMeasurementDataRepository.php
+++ b/logic/Repositories/MariaDBMeasurementDataRepository.php
@@ -37,7 +37,6 @@ class MariaDBMeasurementDataRepository implements IMeasurementDataRepository {
 	
 	/** Function that gets a static variable by it's name. Useful in preparing SQL queries. */
 	private $get_static;
-	
 	private $get_static_extra;
 	
 	function __construct(Database $in_database, TomlConfig $in_settings) {
