Notifications

This commit is contained in:
Roman Hergenreder 2020-04-02 21:19:06 +02:00
parent 541b8563d5
commit d7a5897fc9
24 changed files with 469 additions and 429 deletions

@ -1,8 +1,9 @@
<?php <?php
namespace Api; namespace Api\ApiKey;
class CreateApiKey extends Request { use \Api\Request;
class Create extends Request {
public function __construct($user, $externCall = false) { public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array()); parent::__construct($user, $externCall, array());

@ -1,10 +1,11 @@
<?php <?php
namespace Api; namespace Api\ApiKey;
use \Api\Request;
use \Driver\SQL\Condition\Compare; use \Driver\SQL\Condition\Compare;
class GetApiKeys extends Request { class Fetch extends Request {
public function __construct($user, $externCall = false) { public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array()); parent::__construct($user, $externCall, array());

@ -1,11 +1,12 @@
<?php <?php
namespace Api; namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter; use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare; use \Driver\SQL\Condition\Compare;
class RefreshApiKey extends Request { class Refresh extends Request {
public function __construct($user, $externCall = false) { public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array( parent::__construct($user, $externCall, array(

@ -1,11 +1,12 @@
<?php <?php
namespace Api; namespace Api\ApiKey;
use \Api\Request;
use \Api\Parameter\Parameter; use \Api\Parameter\Parameter;
use \Driver\SQL\Condition\Compare; use \Driver\SQL\Condition\Compare;
class RevokeApiKey extends Request { class Revoke extends Request {
public function __construct($user, $externCall = false) { public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array( parent::__construct($user, $externCall, array(

@ -0,0 +1,135 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Api\Parameter\Parameter;
use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Create extends Request {
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array(
'groupId' => new Parameter('groupId', Parameter::TYPE_INT, true),
'userId' => new Parameter('userId', Parameter::TYPE_INT, true),
'title' => new StringType('title', 32),
'message' => new StringType('message', 256),
));
$this->isPublic = false;
}
private function checkUser($userId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("User")
->where(new Compare("uid", $userId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "User not found";
}
}
return $this->success;
}
private function insertUserNotification($userId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("UserNotification", array("user_id", "notification_id"))
->addRow($userId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function checkGroup($groupId) {
$sql = $this->user->getSQL();
$res = $sql->select($sql->count())
->from("Group")
->where(new Compare("uid", $groupId))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
if ($res[0]["count"] == 0) {
$this->success = false;
$this->lastError = "Group not found";
}
}
return $this->success;
}
private function insertGroupNotification($groupId, $notificationId) {
$sql = $this->user->getSQL();
$res = $sql->insert("GroupNotification", array("group_id", "notification_id"))
->addRow($groupId, $notificationId)
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
return $this->success;
}
private function createNotification($title, $message) {
$sql = $this->user->getSQL();
$res = $sql->insert("Notification", array("title", "message"))
->addRow($title, $message)
->returning("uid")
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
return $sql->getLastInsertId();
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$userId = $this->getParam("userId");
$groupId = $this->getParam("groupId");
$title = $this->getParam("title");
$message = $this->getParam("message");
if (is_null($userId) && is_null($groupId)) {
return $this->createError("Either userId or groupId must be specified.");
} else if(!is_null($userId) && !is_null($groupId)) {
return $this->createError("Only one of userId and groupId must be specified.");
} else if(!is_null($userId)) {
if ($this->checkUser($userId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertUserNotification($userId, $id);
}
}
} else if(!is_null($groupId)) {
if ($this->checkGroup($groupId)) {
$id = $this->createNotification($title, $message);
if ($this->success) {
return $this->insertGroupNotification($groupId, $id);
}
}
}
return $this->success;
}
};
?>

@ -0,0 +1,92 @@
<?php
namespace Api\Notifications;
use \Api\Request;
use \Driver\SQL\Condition\Compare;
class Fetch extends Request {
private $notifications;
public function __construct($user, $externCall = false) {
parent::__construct($user, $externCall, array());
$this->loginRequired = true;
}
private function fetchUserNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("UserNotification", "UserNotification.notification_id", "Notification.uid")
->where(new Compare("UserNotification.user_id", $userId))
->where(new Compare("UserNotification.seen", false))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
private function fetchGroupNotifications() {
$userId = $this->user->getId();
$sql = $this->user->getSQL();
$res = $sql->select($sql->distinct("Notification.uid"), "created_at", "title", "message")
->from("Notification")
->innerJoin("GroupNotification", "GroupNotification.notification_id", "Notification.uid")
->innerJoin("UserGroup", "GroupNotification.group_id", "UserGroup.group_id")
->where(new Compare("UserGroup.user_id", $userId))
->where(new Compare("GroupNotification.seen", false))
->execute();
$this->success = ($res !== FALSE);
$this->lastError = $sql->getLastError();
if ($this->success) {
foreach($res as $row) {
$id = $row["uid"];
if (!isset($this->notifications[$id])) {
$this->notifications[$id] = array(
"uid" => $id,
"title" => $row["title"],
"message" => $row["message"],
"created_at" => $row["created_at"],
);
}
}
}
return $this->success;
}
public function execute($values = array()) {
if(!parent::execute($values)) {
return false;
}
$this->notifications = array();
if ($this->fetchUserNotifications() && $this->fetchGroupNotifications()) {
$this->result["notifications"] = $this->notifications;
}
return $this->success;
}
};
?>

@ -1,10 +1,11 @@
<?php <?php
namespace Api; namespace Api\User;
use Api\Parameter\Parameter; use \Api\Request;
use Api\Parameter\StringType; use \Api\Parameter\Parameter;
use Driver\SQL\Condition\Compare; use \Api\Parameter\StringType;
use \Driver\SQL\Condition\Compare;
class Login extends Request { class Login extends Request {

@ -1,6 +1,8 @@
<?php <?php
namespace Api; namespace Api\User;
use \Api\Request;
class Logout extends Request { class Logout extends Request {

@ -66,8 +66,8 @@ class CreateDatabase {
->unique("name"); ->unique("name");
$queries[] = $sql->insert("Group", array("uid", "name")) $queries[] = $sql->insert("Group", array("uid", "name"))
->addRow(1, "Default") ->addRow(USER_GROUP_DEFAULT, "Default")
->addRow(2, "Administrator"); ->addRow(USER_GROUP_ADMIN, "Administrator");
$queries[] = $sql->createTable("UserGroup") $queries[] = $sql->createTable("UserGroup")
->addInt("user_id") ->addInt("user_id")
@ -76,6 +76,29 @@ class CreateDatabase {
->foreignKey("user_id", "User", "uid") ->foreignKey("user_id", "User", "uid")
->foreignKey("group_id", "Group", "uid"); ->foreignKey("group_id", "Group", "uid");
$queries[] = $sql->createTable("Notification")
->addSerial("uid")
->addDateTime("created_at", false, $sql->currentTimestamp())
->addString("title", 32)
->addString("message", 256)
->primaryKey("uid");
$queries[] = $sql->createTable("UserNotification")
->addInt("user_id")
->addInt("notification_id")
->addBool("seen")
->foreignKey("user_id", "User", "uid")
->foreignKey("notification_id", "Notification", "uid")
->unique("user_id", "notification_id");
$queries[] = $sql->createTable("GroupNotification")
->addInt("group_id")
->addInt("notification_id")
->addBool("seen")
->foreignKey("group_id", "Group", "uid")
->foreignKey("notification_id", "Notification", "uid")
->unique("group_id", "notification_id");
$queries[] = $sql->createTable("ApiKey") $queries[] = $sql->createTable("ApiKey")
->addSerial("uid") ->addSerial("uid")
->addInt("user_id") ->addInt("user_id")

@ -1,84 +0,0 @@
--
-- API
--
CREATE TABLE IF NOT EXISTS Language (
`uid` int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,
`code` VARCHAR(5) UNIQUE NOT NULL,
`name` VARCHAR(32) UNIQUE NOT NULL
);
INSERT INTO Language (`uid`, `code`, `name`) VALUES
(1, 'en_US', 'American English'),
(2, 'de_DE', 'Deutsch Standard')
ON DUPLICATE KEY UPDATE name=name;
CREATE TABLE IF NOT EXISTS User (
`uid` INTEGER NOT NULL AUTO_INCREMENT,
`email` VARCHAR(64) UNIQUE DEFAULT NULL,
`name` VARCHAR(32) UNIQUE NOT NULL,
`salt` varchar(16) NOT NULL,
`password` varchar(64) NOT NULL,
`language_id` int(11) DEFAULT 1,
PRIMARY KEY (`uid`),
FOREIGN KEY (`language_id`) REFERENCES `Language` (`uid`) ON DELETE SET NULL
);
CREATE TABLE IF NOT EXISTS UserInvitation (
`email` VARCHAR(64) NOT NULL,
`token` VARCHAR(36) UNIQUE NOT NULL,
`valid_until` DATETIME NOT NULL
);
CREATE TABLE IF NOT EXISTS UserToken (
`user_id` INTEGER NOT NULL,
`token` VARCHAR(36) NOT NULL,
`type` ENUM('password_reset', 'confirmation') NOT NULL,
`valid_until` DATETIME NOT NULL,
FOREIGN KEY (`user_id`) REFERENCES `User` (`uid`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `Group` (
`gid` INTEGER NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL,
PRIMARY KEY (`gid`),
UNIQUE (`name`)
);
INSERT INTO `Group` (gid, name) VALUES (1, "Default"), (2, "Administrator")
ON DUPLICATE KEY UPDATE name=name;
CREATE TABLE IF NOT EXISTS UserGroup (
`uid` INTEGER NOT NULL,
`gid` INTEGER NOT NULL,
UNIQUE (`uid`, `gid`),
FOREIGN KEY (`uid`) REFERENCES `User` (`uid`),
FOREIGN KEY (`gid`) REFERENCES `Group` (`gid`)
);
CREATE TABLE IF NOT EXISTS Session (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`expires` timestamp NOT NULL,
`user_id` int(11) NOT NULL,
`ipAddress` varchar(45) NOT NULL,
`os` varchar(64) NOT NULL,
`browser` varchar(64) NOT NULL,
`data` JSON NOT NULL DEFAULT '{}',
`stay_logged_in` BOOLEAN DEFAULT TRUE,
PRIMARY KEY (`uid`),
FOREIGN KEY (`user_id`) REFERENCES `User` (`uid`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ApiKey (
`uid` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`api_key` VARCHAR(64) NOT NULL,
`valid_until` DATETIME NOT NULL,
PRIMARY KEY (`uid`),
FOREIGN KEY (`user_id`) REFERENCES `User` (`uid`)
);
CREATE TABLE IF NOT EXISTS ExternalSiteCache (
`url` VARCHAR(256) UNIQUE,
`data` TEXT NOT NULL,
`expires` DATETIME DEFAULT NULL,
);

@ -58,7 +58,7 @@ namespace Documents\Admin {
if(!$document->getUser()->isLoggedIn()) { if(!$document->getUser()->isLoggedIn()) {
$html .= new \Views\Login($document); $html .= new \Views\Login($document);
} else { } else {
$html .= "You are logged in :]"; $html .= new \Views\Admin($document);
} }
return $html; return $html;

@ -114,6 +114,16 @@ namespace Documents\Install {
$step = self::FINISH_INSTALLATION; $step = self::FINISH_INSTALLATION;
if(!$config->isFilePresent("JWT") && !$config->create("JWT", generateRandomString(32))) { if(!$config->isFilePresent("JWT") && !$config->create("JWT", generateRandomString(32))) {
$this->errorString = "Unable to create jwt file"; $this->errorString = "Unable to create jwt file";
} else {
$req = new \Api\Notifications\Create($user);
$success = $req->execute(array(
"title" => "Welcome",
"message" => "Your Web-base was successfully installed. Check out the admin dashboard. Have fun!",
"groupId" => USER_GROUP_ADMIN)
);
if (!$success) {
$this->errorString = $req->getLastError();
}
} }
} }
@ -298,6 +308,10 @@ namespace Documents\Install {
$success = $sql->insert("User", array("name", "salt", "password")) $success = $sql->insert("User", array("name", "salt", "password"))
->addRow($username, $salt, $hash) ->addRow($username, $salt, $hash)
->returning("uid")
->execute()
&& $sql->insert("UserGroup", array("group_id", "user_id"))
->addRow(USER_GROUP_ADMIN, $sql->getLastInsertId())
->execute(); ->execute();
$msg = $sql->getLastError(); $msg = $sql->getLastError();
@ -727,14 +741,6 @@ namespace Documents\Install {
die(json_encode($response)); die(json_encode($response));
} }
/*if($this->currentStep == self::CHECKING_REQUIRMENTS) {
$this->getDocument()->getHead()->addJSCode("
$(document).ready(function() {
retry();
});
");
}*/
$progressSidebar = $this->createProgressSidebar(); $progressSidebar = $this->createProgressSidebar();
$progressMainview = $this->createProgessMainview(); $progressMainview = $this->createProgessMainview();
$errorStyle = ($this->errorString ? '' : ' style="display:none"'); $errorStyle = ($this->errorString ? '' : ' style="display:none"');

@ -36,6 +36,7 @@ class MySQL extends SQL {
return 'mysqli'; return 'mysqli';
} }
// Connection Managment
public function connect() { public function connect() {
if(!is_null($this->connection)) { if(!is_null($this->connection)) {
@ -164,32 +165,8 @@ class MySQL extends SQL {
return ($success && $returnValues) ? $resultRows : $success; return ($success && $returnValues) ? $resultRows : $success;
} }
public function executeCreateTable($createTable) {
$tableName = $createTable->getTableName();
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
$entries = array();
foreach($createTable->getColumns() as $column) {
$entries[] = ($tmp = $this->getColumnDefinition($column));
if (is_null($tmp)) {
return false;
}
}
foreach($createTable->getConstraints() as $constraint) {
$entries[] = ($tmp = $this->getConstraintDefinition($constraint));
if (is_null($tmp)) {
return false;
}
}
$entries = implode(",", $entries);
$query = "CREATE TABLE$ifNotExists `$tableName` ($entries)";
return $this->execute($query);
}
public function executeInsert($insert) { public function executeInsert($insert) {
$tableName = $insert->getTableName(); $tableName = $this->tableName($insert->getTableName());
$columns = $insert->getColumns(); $columns = $insert->getColumns();
$rows = $insert->getRows(); $rows = $insert->getRows();
$onDuplicateKey = $insert->onDuplicateKey() ?? ""; $onDuplicateKey = $insert->onDuplicateKey() ?? "";
@ -204,7 +181,7 @@ class MySQL extends SQL {
$numColumns = count($rows[0]); $numColumns = count($rows[0]);
} else { } else {
$numColumns = count($columns); $numColumns = count($columns);
$columns = " (`" . implode("`, `", $columns) . "`)"; $columns = " (" . $this->columnName($columns) . ")";
} }
$numRows = count($rows); $numRows = count($rows);
@ -235,7 +212,7 @@ class MySQL extends SQL {
} }
} }
$query = "INSERT INTO `$tableName`$columns VALUES$values$onDuplicateKey"; $query = "INSERT INTO $tableName$columns VALUES$values$onDuplicateKey";
$success = $this->execute($query, $parameters); $success = $this->execute($query, $parameters);
if($success) { if($success) {
@ -247,19 +224,14 @@ class MySQL extends SQL {
public function executeSelect($select) { public function executeSelect($select) {
$columns = array(); $columns = $this->columnName($select->getColumns());
foreach($select->getColumns() as $col) {
$columns[] = $this->columnName($col);
}
$columns = implode(",", $columns);
$tables = $select->getTables(); $tables = $select->getTables();
$params = array(); $params = array();
if (is_null($tables) || empty($tables)) { if (is_null($tables) || empty($tables)) {
return "SELECT $columns"; return "SELECT $columns";
} else { } else {
$tables = implode(",", $tables); $tables = $this->tableName($tables);
} }
$conditions = $select->getConditions(); $conditions = $select->getConditions();
@ -275,9 +247,9 @@ class MySQL extends SQL {
$joinStr = ""; $joinStr = "";
foreach($joins as $join) { foreach($joins as $join) {
$type = $join->getType(); $type = $join->getType();
$joinTable = $join->getTable(); $joinTable = $this->tableName($join->getTable());
$columnA = $join->getColumnA(); $columnA = $this->columnName($join->getColumnA());
$columnB = $join->getColumnB(); $columnB = $this->columnName($join->getColumnB());
$joinStr .= " $type JOIN $joinTable ON $columnA=$columnB"; $joinStr .= " $type JOIN $joinTable ON $columnA=$columnB";
} }
} }
@ -399,8 +371,10 @@ class MySQL extends SQL {
// TODO: check this please.. // TODO: check this please..
public function getValueDefinition($value) { public function getValueDefinition($value) {
if (is_numeric($value) || is_bool($value)) { if (is_numeric($value)) {
return $value; return $value;
} else if(is_bool($value)) {
return $value ? "TRUE" : "FALSE";
} else if(is_null($value)) { } else if(is_null($value)) {
return "NULL"; return "NULL";
} else if($value instanceof Keyword) { } else if($value instanceof Keyword) {
@ -421,12 +395,22 @@ class MySQL extends SQL {
} }
protected function tableName($table) { protected function tableName($table) {
return "`$table`"; if (is_array($table)) {
$tables = array();
foreach($table as $t) $tables[] = $this->tableName($t);
return implode(",", $tables);
} else {
return "`$table`";
}
} }
protected function columnName($col) { protected function columnName($col) {
if ($col instanceof Keyword) { if ($col instanceof Keyword) {
return $col->getValue(); return $col->getValue();
} elseif(is_array($col)) {
$columns = array();
foreach($col as $c) $columns[] = $this->columnName($c);
return implode(",", $columns);
} else { } else {
if (($index = strrpos($col, ".")) !== FALSE) { if (($index = strrpos($col, ".")) !== FALSE) {
$tableName = $this->tableName(substr($col, 0, $index)); $tableName = $this->tableName(substr($col, 0, $index));
@ -446,12 +430,4 @@ class MySQL extends SQL {
return new Keyword("NOW()"); return new Keyword("NOW()");
} }
public function count($col = NULL) {
if (is_null($col)) {
return new Keyword("COUNT(*) AS count");
} else {
return new Keyword("COUNT($col) AS count");
}
}
}; };

@ -36,15 +36,6 @@ class PostgreSQL extends SQL {
return 'pgsql'; return 'pgsql';
} }
public function getLastError() {
$lastError = parent::getLastError();
if (empty($lastError)) {
$lastError = pg_last_error($this->connection) . " " . pg_last_error($this->connection);
}
return $lastError;
}
// Connection Managment // Connection Managment
public function connect() { public function connect() {
if(!is_null($this->connection)) { if(!is_null($this->connection)) {
@ -84,6 +75,15 @@ class PostgreSQL extends SQL {
pg_close($this->connection); pg_close($this->connection);
} }
public function getLastError() {
$lastError = parent::getLastError();
if (empty($lastError)) {
$lastError = pg_last_error($this->connection) . " " . pg_last_error($this->connection);
}
return $lastError;
}
protected function execute($query, $values = NULL, $returnValues = false) { protected function execute($query, $values = NULL, $returnValues = false) {
$this->lastError = ""; $this->lastError = "";
@ -136,30 +136,6 @@ class PostgreSQL extends SQL {
} }
// Querybuilder // Querybuilder
public function executeCreateTable($createTable) {
$tableName = $this->tableName($createTable->getTableName());
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
$entries = array();
foreach($createTable->getColumns() as $column) {
$entries[] = ($tmp = $this->getColumnDefinition($column));
if (is_null($tmp)) {
return false;
}
}
foreach($createTable->getConstraints() as $constraint) {
$entries[] = ($tmp = $this->getConstraintDefinition($constraint));
if (is_null($tmp)) {
return false;
}
}
$entries = implode(",", $entries);
$query = "CREATE TABLE$ifNotExists $tableName ($entries)";
return $this->execute($query);
}
public function executeInsert($insert) { public function executeInsert($insert) {
$tableName = $this->tableName($insert->getTableName()); $tableName = $this->tableName($insert->getTableName());
@ -408,7 +384,7 @@ class PostgreSQL extends SQL {
if ($val instanceof Keyword) { if ($val instanceof Keyword) {
return $val->getValue(); return $val->getValue();
} else { } else {
$params[] = $val; $params[] = is_bool($val) ? ($val ? "TRUE" : "FALSE") : $val;
return '$' . count($params); return '$' . count($params);
} }
} }
@ -449,13 +425,5 @@ class PostgreSQL extends SQL {
public function currentTimestamp() { public function currentTimestamp() {
return new Keyword("CURRENT_TIMESTAMP"); return new Keyword("CURRENT_TIMESTAMP");
} }
public function count($col = NULL) {
if (is_null($col)) {
return new Keyword("COUNT(*) AS count");
} else {
return new Keyword("COUNT(" . $this->columnName($col) . ") AS count");
}
}
} }
?> ?>

@ -63,7 +63,30 @@ abstract class SQL {
// TODO: pull code duplicates up // TODO: pull code duplicates up
// Querybuilder // Querybuilder
public abstract function executeCreateTable($query); public function executeCreateTable($createTable) {
$tableName = $this->tableName($createTable->getTableName());
$ifNotExists = $createTable->ifNotExists() ? " IF NOT EXISTS": "";
$entries = array();
foreach($createTable->getColumns() as $column) {
$entries[] = ($tmp = $this->getColumnDefinition($column));
if (is_null($tmp)) {
return false;
}
}
foreach($createTable->getConstraints() as $constraint) {
$entries[] = ($tmp = $this->getConstraintDefinition($constraint));
if (is_null($tmp)) {
return false;
}
}
$entries = implode(",", $entries);
$query = "CREATE TABLE$ifNotExists $tableName ($entries)";
return $this->execute($query);
}
public abstract function executeInsert($query); public abstract function executeInsert($query);
public abstract function executeSelect($query); public abstract function executeSelect($query);
public abstract function executeDelete($query); public abstract function executeDelete($query);
@ -79,7 +102,20 @@ abstract class SQL {
// Special Keywords and functions // Special Keywords and functions
public abstract function currentTimestamp(); public abstract function currentTimestamp();
public abstract function count($col = NULL);
public function count($col = NULL) {
if (is_null($col)) {
return new Keyword("COUNT(*) AS count");
} else {
$col = $this->columnName($col);
return new Keyword("COUNT($col) AS count");
}
}
public function distinct($col) {
$col = $this->columnName($col);
return new Keyword("DISTINCT($col)");
}
// Statements // Statements
protected abstract function execute($query, $values=NULL, $returnValues=false); protected abstract function execute($query, $values=NULL, $returnValues=false);

@ -26,8 +26,8 @@ class JWT
* @param bool $verify Don't skip verification process * @param bool $verify Don't skip verification process
* *
* @return object The JWT's payload as a PHP object * @return object The JWT's payload as a PHP object
* @throws UnexpectedValueException Provided JWT was invalid * @throws \UnexpectedValueException Provided JWT was invalid
* @throws DomainException Algorithm was not provided * @throws \DomainException Algorithm was not provided
* *
* @uses jsonDecode * @uses jsonDecode
* @uses urlsafeB64Decode * @uses urlsafeB64Decode
@ -36,22 +36,22 @@ class JWT
{ {
$tks = explode('.', $jwt); $tks = explode('.', $jwt);
if (count($tks) != 3) { if (count($tks) != 3) {
throw new UnexpectedValueException('Wrong number of segments'); throw new \UnexpectedValueException('Wrong number of segments');
} }
list($headb64, $bodyb64, $cryptob64) = $tks; list($headb64, $bodyb64, $cryptob64) = $tks;
if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) { if (null === ($header = JWT::jsonDecode(JWT::urlsafeB64Decode($headb64)))) {
throw new UnexpectedValueException('Invalid segment encoding'); throw new \UnexpectedValueException('Invalid segment encoding');
} }
if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) { if (null === $payload = JWT::jsonDecode(JWT::urlsafeB64Decode($bodyb64))) {
throw new UnexpectedValueException('Invalid segment encoding'); throw new \UnexpectedValueException('Invalid segment encoding');
} }
$sig = JWT::urlsafeB64Decode($cryptob64); $sig = JWT::urlsafeB64Decode($cryptob64);
if ($verify) { if ($verify) {
if (empty($header->alg)) { if (empty($header->alg)) {
throw new DomainException('Empty algorithm'); throw new \DomainException('Empty algorithm');
} }
if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) { if ($sig != JWT::sign("$headb64.$bodyb64", $key, $header->alg)) {
throw new UnexpectedValueException('Signature verification failed'); throw new \UnexpectedValueException('Signature verification failed');
} }
} }
return $payload; return $payload;
@ -93,7 +93,7 @@ class JWT
* algorithms are 'HS256', 'HS384' and 'HS512' * algorithms are 'HS256', 'HS384' and 'HS512'
* *
* @return string An encrypted message * @return string An encrypted message
* @throws DomainException Unsupported algorithm was specified * @throws \DomainException Unsupported algorithm was specified
*/ */
public static function sign($msg, $key, $method = 'HS256') public static function sign($msg, $key, $method = 'HS256')
{ {
@ -103,7 +103,7 @@ class JWT
'HS512' => 'sha512', 'HS512' => 'sha512',
); );
if (empty($methods[$method])) { if (empty($methods[$method])) {
throw new DomainException('Algorithm not supported'); throw new \DomainException('Algorithm not supported');
} }
return hash_hmac($methods[$method], $msg, $key, true); return hash_hmac($methods[$method], $msg, $key, true);
} }
@ -114,7 +114,7 @@ class JWT
* @param string $input JSON string * @param string $input JSON string
* *
* @return object Object representation of JSON string * @return object Object representation of JSON string
* @throws DomainException Provided string was invalid JSON * @throws \DomainException Provided string was invalid JSON
*/ */
public static function jsonDecode($input) public static function jsonDecode($input)
{ {
@ -122,7 +122,7 @@ class JWT
if (function_exists('json_last_error') && $errno = json_last_error()) { if (function_exists('json_last_error') && $errno = json_last_error()) {
JWT::_handleJsonError($errno); JWT::_handleJsonError($errno);
} else if ($obj === null && $input !== 'null') { } else if ($obj === null && $input !== 'null') {
throw new DomainException('Null result with non-null input'); throw new \DomainException('Null result with non-null input');
} }
return $obj; return $obj;
} }
@ -133,7 +133,7 @@ class JWT
* @param object|array $input A PHP object or array * @param object|array $input A PHP object or array
* *
* @return string JSON representation of the PHP object or array * @return string JSON representation of the PHP object or array
* @throws DomainException Provided object could not be encoded to valid JSON * @throws \DomainException Provided object could not be encoded to valid JSON
*/ */
public static function jsonEncode($input) public static function jsonEncode($input)
{ {
@ -141,7 +141,7 @@ class JWT
if (function_exists('json_last_error') && $errno = json_last_error()) { if (function_exists('json_last_error') && $errno = json_last_error()) {
JWT::_handleJsonError($errno); JWT::_handleJsonError($errno);
} else if ($json === 'null' && $input !== null) { } else if ($json === 'null' && $input !== null) {
throw new DomainException('Null result with non-null input'); throw new \DomainException('Null result with non-null input');
} }
return $json; return $json;
} }
@ -189,7 +189,7 @@ class JWT
JSON_ERROR_CTRL_CHAR => 'Unexpected control character found', JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON' JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON'
); );
throw new DomainException( throw new \DomainException(
isset($messages[$errno]) isset($messages[$errno])
? $messages[$errno] ? $messages[$errno]
: 'Unknown JSON error: ' . $errno : 'Unknown JSON error: ' . $errno

@ -2,6 +2,7 @@
namespace Objects; namespace Objects;
use \External\JWT;
use Driver\SQL\Column\Column; use Driver\SQL\Column\Column;
use Driver\SQL\Condition\Compare; use Driver\SQL\Condition\Compare;
use Driver\SQL\Condition\CondBool; use Driver\SQL\Condition\CondBool;
@ -132,8 +133,6 @@ class User extends ApiObject {
$this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
} }
} }
} else {
var_dump($this->sql->getLastError());
} }
return $success; return $success;
@ -146,7 +145,7 @@ class User extends ApiObject {
&& ($jwt = $this->configuration->getJWT())) { && ($jwt = $this->configuration->getJWT())) {
try { try {
$token = $_COOKIE['session']; $token = $_COOKIE['session'];
$decoded = (array)\External\JWT::decode($token, $jwt->getKey()); $decoded = (array)JWT::decode($token, $jwt->getKey());
if(!is_null($decoded)) { if(!is_null($decoded)) {
$userId = (isset($decoded['userId']) ? $decoded['userId'] : NULL); $userId = (isset($decoded['userId']) ? $decoded['userId'] : NULL);
$sessionId = (isset($decoded['sessionId']) ? $decoded['sessionId'] : NULL); $sessionId = (isset($decoded['sessionId']) ? $decoded['sessionId'] : NULL);
@ -154,8 +153,8 @@ class User extends ApiObject {
$this->readData($userId, $sessionId); $this->readData($userId, $sessionId);
} }
} }
} catch(Exception $e) { } catch(\Exception $e) {
echo $e; // ignored
} }
} }
@ -204,8 +203,6 @@ class User extends ApiObject {
$this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName'])); $this->setLangauge(Language::newInstance($row['langId'], $row['langCode'], $row['langName']));
} }
} }
} else {
var_dump($this->sql->getLastError());
} }
return $success; return $success;

@ -0,0 +1,47 @@
<?php
namespace Views;
// Source: https://adminlte.io/themes/v3/
class Admin extends \View {
public function __construct($document) {
parent::__construct($document);
}
private function getMainHeader() {
$home = L("Home");
$search = L("Search");
$iconMenu = $this->createIcon("bars");
$iconSearch = $this->createIcon("search");
$iconNotifications = $this->createIcon("bell");
$header = "";
return $header;
}
private function getMainContent() {
return "";
}
private function getSideBar() {
return "";
}
public function getCode() {
$html = parent::getCode();
$html .= "<div class=\"main-wrapper\">";
$html .= $this->getMainHeader();
$html .= "<div id=\"content\">";
$html .= $this->getSideBar();
$html .= $this->getMainContent();
$html .= "</div>
</div>";
return $html;
}
}
?>

@ -49,8 +49,6 @@ class Login extends \View {
</div>"; </div>";
return $html; return $html;
return $html;
} }
} }

@ -27,203 +27,27 @@
vertical-align: bottom; vertical-align: bottom;
} }
.device-table > tbody > tr:hover { .main-header {
cursor: pointer; border-bottom: 1px solid #dee2e6;
background-color: grey;
} }
.device-table > tbody > tr > td:nth-child(3) { .main-sidebar {
text-align: center; background-color: #343a40;
width: 250px;
position: fixed;
top: 0;
left: 0;
height: 100vh;
z-index: 999;
color: #fff;
transition: all 0.3s;
} }
.apikey-table > tbody > tr > td:last-child { .content-wrapper {
float: right; background: #f4f6f9;
} }
.apikey-table > tbody > tr > td:first-child { .main-wrapper {
word-break: break-all; display: flex;
}
.apikey-table > tbody > tr:hover {
background-color: grey;
cursor: pointer;
}
.sidebar {
margin: 0;
padding: 0;
color: white;
text-align: center;
}
.sidebar a {
color: white;
}
.status { font-size: 12px; }
.status-ok { color: #38e40d; }
.status-error { color: red; }
.status-offline { color: gray; }
.sidebar .nav-item {
line-height: 30px;
border-bottom: 1px solid gray;
}
.nav-link {
grid-template-columns: 20px auto 20px;
}
.nav-link-center {
grid-column-start: 2;
grid-column-end: 2;
}
.sidebar-title {
font-size: 18px;
margin-bottom: 0.5em;
font-weight: bold;
}
.sidebar-top {
height: 100px;
padding: 10px;
border-bottom: 5px solid gray;
background-color: #555;
}
.sidebar-bottom {
height: 100px;
padding: 10px;
position: absolute;
bottom: 0;
border-top: 5px solid gray;
width: 100%; width: 100%;
grid-template-rows: 50% 50%;
}
.grid {
display: grid;
}
.grid > span {
align-self: center;
}
.sidebar-bottom > a {
grid-template-columns: 20px auto;
align-self: center;
}
.sidebar-bottom > a:hover {
text-decoration: none;
font-weight: bold;
}
.sidebar .active .nav-link-center {
text-decoration: underline;
font-weight: bold;
}
.sidebar .nav-item:hover {
background-color: gray;
}
.nav-device {
background-color: #5a5a5a;
}
.service {
margin: 10px;
}
.service > .card-header {
color: black;
cursor: pointer;
display: grid;
grid-template-columns: 20px auto;
}
.service-icon {
width: 32px;
height: 32px;
}
.fs-listview > tbody > tr:hover {
cursor: pointer;
}
.fs-listview > tbody > tr.downloading > td, .fs-gridview > div.downloading > span {
font-style: italic;
color: gray;
}
.fs-toolbar {
display: grid;
grid-template-columns: 1px 40px 40px 1px auto 1px 40px 1px 40px 40px 1px;
}
.fs-toolbar > i {
align-self: center;
cursor: pointer;
}
.fs-toolbar > span {
text-align: left;
align-self: center;
}
.fs-gridview {
display: grid;
grid-template-columns: repeat(4, auto);
}
.fs-gridview div {
align-self: center;
text-align: center;
}
.fs-gridview > div {
padding: 5px;
display: grid;
grid-template-rows: 48px auto;
}
.fs-gridview > div:hover {
background: #ddd;
cursor: pointer;
}
.vr {
border-left: 1px solid #dee2e6;
height: 100%;
}
.camera-stream {
cursor: pointer;
}
.temperature-controls {
display: grid;
grid-template-columns: auto 30px;
}
.temperature-controls > div {
align-self: center;
text-align: left;
}
.shell-tabs > li > a {
color: black;
line-height: inherit;
outline: none;
}
.shell {
text-align: left
}
.speaker-controls {
display:grid;
grid-template-columns: auto auto auto;
} }

@ -44,9 +44,9 @@ if(isset($_GET["api"]) && is_string($_GET["api"])) {
header("400 Bad Request"); header("400 Bad Request");
$response = createError("Invalid Method"); $response = createError("Invalid Method");
} else { } else {
$apiFunction = strtoupper($apiFunction[0]) . substr($apiFunction, 1); $apiFunction = implode("\\", array_map('ucfirst', explode("/", $apiFunction)));
$apiFunction = str_replace("/", "\\", $apiFunction); if($apiFunction[0] !== "\\") $apiFunction = "\\$apiFunction";
$class = "\\Api\\$apiFunction"; $class = "\\Api$apiFunction";
$file = getClassPath($class); $file = getClassPath($class);
if(!file_exists($file)) { if(!file_exists($file)) {
header("404 Not Found"); header("404 Not Found");
@ -63,10 +63,15 @@ if(isset($_GET["api"]) && is_string($_GET["api"])) {
} }
} }
} else { } else {
$documentName = $_GET["site"];
if ($installation) { if ($installation) {
$document = new Documents\Install($user); if ($documentName !== "" && $documentName !== "index.php") {
$response = "Redirecting to <a href=\"/\">/</a>";
header("Location: /");
} else {
$document = new Documents\Install($user);
}
} else { } else {
$documentName = $_GET["site"];
if(empty($documentName) || strcasecmp($documentName, "install") === 0) { if(empty($documentName) || strcasecmp($documentName, "install") === 0) {
$documentName = "home"; $documentName = "home";
} else if(!preg_match("/[a-zA-Z]+(\/[a-zA-Z]+)*/", $documentName)) { } else if(!preg_match("/[a-zA-Z]+(\/[a-zA-Z]+)*/", $documentName)) {

@ -11,7 +11,7 @@ $(document).ready(function() {
errorDiv.hide(); errorDiv.hide();
btn.prop("disabled", true); btn.prop("disabled", true);
btn.html("Logging in… <i class=\"fa fa-spin fa-circle-notch\"></i>"); btn.html("Logging in… <i class=\"fa fa-spin fa-circle-notch\"></i>");
jsCore.apiCall("login", {"username": username, "password": password}, function(data) { jsCore.apiCall("user/login", {"username": username, "password": password}, function(data) {
window.location.reload(); window.location.reload();
}, function(err) { }, function(err) {
btn.html("Login"); btn.html("Login");

@ -123,7 +123,7 @@ $(document).ready(function() {
}); });
$("#btnFinish").click(function() { $("#btnFinish").click(function() {
window.location.reload(); window.location = "/admin";
}); });
$("#btnRetry").click(function() { $("#btnRetry").click(function() {

@ -6,10 +6,16 @@ class ApiTestCase(PhpTest):
super().__init__({ super().__init__({
"Testing login…": self.test_login, "Testing login…": self.test_login,
"Testing already logged in…": self.test_already_logged_in, "Testing already logged in…": self.test_already_logged_in,
# ApiKeys
"Testing get api keys empty…": self.test_get_api_keys_empty, "Testing get api keys empty…": self.test_get_api_keys_empty,
"Testing create api key…": self.test_create_api_key, "Testing create api key…": self.test_create_api_key,
"Testing referesh api key…": self.test_refresh_api_key, "Testing referesh api key…": self.test_refresh_api_key,
"Testing revoke api key…": self.test_revoke_api_key, "Testing revoke api key…": self.test_revoke_api_key,
# Notifications
"Testing fetch notifications…": self.test_fetch_notifications,
"Testing logout…": self.test_logout, "Testing logout…": self.test_logout,
}) })
@ -17,12 +23,12 @@ class ApiTestCase(PhpTest):
return "/api/%s" % method return "/api/%s" % method
def getApiKeys(self): def getApiKeys(self):
obj = self.httpPost(self.api("getApiKeys")) obj = self.httpPost(self.api("apiKey/fetch"))
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
return obj return obj
def test_login(self): def test_login(self):
obj = self.httpPost(self.api("login"), data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD }) obj = self.httpPost(self.api("user/login"), data={ "username": PhpTest.ADMIN_USERNAME, "password": PhpTest.ADMIN_PASSWORD })
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
return obj return obj
@ -35,7 +41,7 @@ class ApiTestCase(PhpTest):
self.assertEquals([], obj["api_keys"]) self.assertEquals([], obj["api_keys"])
def test_create_api_key(self): def test_create_api_key(self):
obj = self.httpPost(self.api("createApiKey")) obj = self.httpPost(self.api("apiKey/create"))
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
self.assertTrue("api_key" in obj) self.assertTrue("api_key" in obj)
self.apiKey = obj["api_key"] self.apiKey = obj["api_key"]
@ -45,18 +51,22 @@ class ApiTestCase(PhpTest):
self.assertDictEqual(self.apiKey, obj["api_keys"][0]) self.assertDictEqual(self.apiKey, obj["api_keys"][0])
def test_refresh_api_key(self): def test_refresh_api_key(self):
obj = self.httpPost(self.api("refreshApiKey"), data={"id": self.apiKey["uid"]}) obj = self.httpPost(self.api("apiKey/refresh"), data={"id": self.apiKey["uid"]})
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
self.assertTrue("valid_until" in obj) self.assertTrue("valid_until" in obj)
self.assertTrue(obj["valid_until"] >= self.apiKey["valid_until"]) self.assertTrue(obj["valid_until"] >= self.apiKey["valid_until"])
def test_revoke_api_key(self): def test_revoke_api_key(self):
obj = self.httpPost(self.api("revokeApiKey"), data={"id": self.apiKey["uid"]}) obj = self.httpPost(self.api("apiKey/revoke"), data={"id": self.apiKey["uid"]})
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
self.test_get_api_keys_empty() self.test_get_api_keys_empty()
def test_logout(self): def test_fetch_notifications(self):
obj = self.httpPost(self.api("logout")) obj = self.httpPost(self.api("notifications/fetch"))
self.assertEquals(True, obj["success"], obj["msg"]) self.assertEquals(True, obj["success"], obj["msg"])
obj = self.httpPost(self.api("logout"))
def test_logout(self):
obj = self.httpPost(self.api("user/logout"))
self.assertEquals(True, obj["success"], obj["msg"])
obj = self.httpPost(self.api("user/logout"))
self.assertEquals(False, obj["success"]) self.assertEquals(False, obj["success"])