From b46cee71f79795f7300c275f2cfea7fca27a752d Mon Sep 17 00:00:00 2001 From: Camil Staps Date: Tue, 7 Feb 2017 17:49:10 +0100 Subject: Web interface --- db/install.sql | 20 +---- frontend/Author.php | 45 ++++++++++ frontend/Dockerfile | 2 +- frontend/Model.php | 138 +++++++++++++++++++++++++++++++ frontend/Package.php | 18 ++++ frontend/Version.php | 10 +++ frontend/conf.php | 20 ++++- frontend/foot.php | 2 + frontend/head.php | 20 +++++ frontend/index.php | 229 +++++++++++++++++++++++++++++++++++++++++++++++++++ frontend/list.php | 40 +++------ 11 files changed, 496 insertions(+), 48 deletions(-) create mode 100644 frontend/Author.php create mode 100644 frontend/Model.php create mode 100644 frontend/Package.php create mode 100644 frontend/Version.php create mode 100644 frontend/foot.php create mode 100644 frontend/head.php create mode 100644 frontend/index.php diff --git a/db/install.sql b/db/install.sql index f58cf5b..fbcf340 100644 --- a/db/install.sql +++ b/db/install.sql @@ -1,20 +1,8 @@ CREATE TABLE `author` ( `id` tinyint(4) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `name` varchar(64) NOT NULL, - `email` varchar(256) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1; - -CREATE TABLE `job` ( - `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, - `package_id` smallint(5) UNSIGNED NOT NULL, - `major` tinyint(3) UNSIGNED NOT NULL, - `minor` tinyint(3) UNSIGNED NOT NULL, - `revision` tinyint(3) UNSIGNED NOT NULL, - `time_scheduled` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `time_started` datetime DEFAULT NULL, - `time_finished` datetime DEFAULT NULL, - `result_code` tinyint(3) UNSIGNED DEFAULT NULL, - `log` text + `name` varchar(63) NOT NULL, + `email` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=latin1; CREATE TABLE `package` ( @@ -22,6 +10,7 @@ CREATE TABLE `package` ( `author_id` tinyint(3) UNSIGNED NOT NULL, `name` varchar(63) NOT NULL, `url` varchar(2047) NOT NULL, + `git_url` varchar(2047) NOT NULL, `desc` text NOT NULL, `time_added` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=latin1; @@ -37,7 +26,6 @@ CREATE TABLE `version` ( ) ENGINE=InnoDB DEFAULT CHARSET=latin1; ALTER TABLE `author` - ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `name` (`name`); ALTER TABLE `package` diff --git a/frontend/Author.php b/frontend/Author.php new file mode 100644 index 0000000..669e733 --- /dev/null +++ b/frontend/Author.php @@ -0,0 +1,45 @@ +pdo, ['`author_id`=?'], [$this->id]); + } + + public function getPackages() { + return Package::search($this->pdo, ['`author_id`=?'], [$this->id]); + } + + public static function hash($password) { + return password_hash($password, PASSWORD_DEFAULT, ['cost' => 10]); + } + + protected static function mutator($key, $value) { + switch ($key) { + case 'password': + return self::hash($value); + default: + return parent::mutator($key, $value); + } + } + + public function isAdmin() { + return in_array($this->id, Constants::user_admins); + } + + public function verifyPassword($password) { + if (!password_verify($password, $this->password)) + return false; + if (password_needs_rehash( + $this->password, PASSWORD_DEFAULT, ['cost' => 10])) + $this->password = $password; + return true; + } +} diff --git a/frontend/Dockerfile b/frontend/Dockerfile index e9e468b..73b4a60 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,3 +1,3 @@ FROM php:apache -RUN docker-php-ext-install mysqli +RUN docker-php-ext-install pdo pdo_mysql diff --git a/frontend/Model.php b/frontend/Model.php new file mode 100644 index 0000000..0f70aa2 --- /dev/null +++ b/frontend/Model.php @@ -0,0 +1,138 @@ +pdo = $pdo; + + $stmt = $this->pdo->prepare("SELECT * FROM `".self::table()."` WHERE `".static::$primary_key."`=?"); + $stmt->execute([$id]); + if ($stmt->rowCount() == 0) { + throw new ModelNotFoundException("The ".static::$table." with id '$id' could not be found."); + } + $this->data = $stmt->fetch(PDO::FETCH_ASSOC); + } + + public function __set($key, $value) { + if (!in_array($key, static::$fillable_columns)) { + throw new ModelIllegalAccessException("Column `".self::table()."`.`$key` cannot be edited."); + } + if ($this->data[$key] == $value) { + return; + } + $stmt = $this->pdo->prepare("UPDATE `".self::table()."` SET `$key`=? WHERE `".static::$primary_key."`=?"); + $stmt->execute([ + $this->mutator($key, $value), + $this->data[static::$primary_key] + ]); + if ($stmt->rowCount() != 1) { + throw new ModelSetFailedException("Failed to update `".self::table()."`.`$key` to '$value'."); + } + $this->data[$key] = $value; + } + + public function __get($key) { + return $this->accessor($key, $this->data[$key]); + } + + public static function create($pdo, $values) { + $class = get_called_class(); + + $columns = array_combine(static::$fillable_columns, $values); + $questions = []; + + foreach ($columns as $column => $value) { + $columns[$column] = $class::mutator($column, $value); + $questions[] = '?'; + } + + $stmt = $pdo->prepare( + "INSERT INTO `".self::table()."` " . + "(`" . implode('`, `', array_keys($columns)) . "`) " . + "VALUES (" . implode(',', $questions) . ")"); + $stmt->execute(array_values($columns)); + + if ($stmt->rowCount() != 1) + throw new ModelCreateFailedException(); + + return new $class($pdo, $pdo->lastInsertId()); + } + + public static function searchIds($pdo, $where = [], $values = []) { + $stmt = $pdo->prepare("SELECT `id` FROM `".static::table()."`" . ((count($where) > 0) ? (" WHERE (" . implode(') AND (', $where) . ")") : "")); + $stmt->execute($values); + + $ids = []; + foreach ($stmt->fetchAll(PDO::FETCH_ASSOC) as $row) { + $ids[] = $row['id']; + } + return $ids; + } + + public static function search($pdo, $where = [], $values = []) { + $class = get_called_class(); + + $items = []; + foreach (self::searchIds($pdo, $where, $values) as $id) { + $items[] = new $class($pdo, $id); + } + return $items; + } + + public static function count($pdo, $where = [], $values = []) { + $class = get_called_class(); + + $stmt = $pdo->prepare("SELECT COUNT(*) FROM `".static::table()."`" . ((count($where) > 0) ? (" WHERE (" . implode(') AND (', $where) . ")") : "")); + $stmt->execute($values); + + return $stmt->fetchColumn(); + } + + protected static function accessor($key, $value) { + if (is_null($value)) { + return null; + } elseif (in_array($key, static::$booleans)) { + return (bool) $value; + } elseif (in_array($key, static::$dates) || in_array($key, static::$timestamps)) { + return strtotime($value); + } else { + return $value; + } + } + + protected static function mutator($key, $value) { + if (in_array($key, static::$dates) && is_int($value)) { + return date('Y-m-d', $value); + } elseif (in_array($key, static::$timestamps) && is_int($value)) { + return date('Y-m-d H:i:s', $value); + } else { + return (string) $value; + } + } + + public function delete() { + $stmt = $this->pdo->prepare("DELETE FROM `".self::table()."` WHERE `".static::$primary_key."`=?"); + $stmt->execute([$this->data[static::$primary_key]]); + return $stmt->rowCount() != 0; + } + + private static function table() { + return self::TABLE_PREFIX . static::$table; + } +} diff --git a/frontend/Package.php b/frontend/Package.php new file mode 100644 index 0000000..0b2afed --- /dev/null +++ b/frontend/Package.php @@ -0,0 +1,18 @@ +pdo, $this->author_id); + } + + public function getVersionIds() { + return Version::searchIds($this->pdo, ['`package_id`=?'], [$this->id]); + } + + public function getVersions() { + return Version::search($this->pdo, ['`package_id`=?'], [$this->id]); + } +} diff --git a/frontend/Version.php b/frontend/Version.php new file mode 100644 index 0000000..39d3dd2 --- /dev/null +++ b/frontend/Version.php @@ -0,0 +1,10 @@ +pdo, $this->package_id); + } +} diff --git a/frontend/conf.php b/frontend/conf.php index f47a7cb..dea8c25 100644 --- a/frontend/conf.php +++ b/frontend/conf.php @@ -3,7 +3,21 @@ define('DB_HOST', 'db'); define('DB_NAME', 'clpmdb'); define('DB_USER', 'clpm'); define('DB_PASS', 'clpm'); +define('DB_PORT', 3306); -$db = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME); -if (mysqli_connect_errno()) - die('Connection to the database failed.'); +session_start(); + +try { + $_pdo = new PDO("mysql:host=".DB_HOST.";port=".DB_PORT.";dbname=".DB_NAME.";charset=utf8", DB_USER, DB_PASS); + $_pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $_pdo->setAttribute(PDO::ATTR_ORACLE_NULLS, PDO::NULL_NATURAL); + $_pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); +} catch (PDOException $e) { + die("Down until PDO error fixed."); +} + +spl_autoload_register(function ($pClass) { + $path = __DIR__ . "/$pClass.php"; + if (file_exists($path)) + require_once($path); +}); diff --git a/frontend/foot.php b/frontend/foot.php new file mode 100644 index 0000000..308b1d0 --- /dev/null +++ b/frontend/foot.php @@ -0,0 +1,2 @@ + + diff --git a/frontend/head.php b/frontend/head.php new file mode 100644 index 0000000..8664773 --- /dev/null +++ b/frontend/head.php @@ -0,0 +1,20 @@ + + + + CLPM + + + + +

CLPM

+
diff --git a/frontend/index.php b/frontend/index.php new file mode 100644 index 0000000..373052f --- /dev/null +++ b/frontend/index.php @@ -0,0 +1,229 @@ + + Your account has been created. + Your name is name?> and your password is . + Store this password in a secure place. + ' . $e->getMessage() . '.

'; + } +} + +if (isset($_POST['login'])) { + $authors = Author::search($_pdo, ['`name` like ?'], [$_POST['login_name']]); + if (count($authors) == 0) { + echo '

No such user.

'; + } else { + $author = $authors[0]; + if ($author->verifyPassword($_POST['login_pass'])) { + $_SESSION['logged_in_id'] = $author->id; + } else { + echo '

Invalid password.

'; + } + } +} + +if (isset($_SESSION['logged_in_id'])) { + $_author = new Author($_pdo, $_SESSION['logged_in_id']); + echo "You are logged in as {$_author->name}. Logout.
"; + + if (isset($_POST['create_pkg'])) { + try { + $pkg = Package::create($_pdo, + [ $_author->id + , $_POST['create_pkg_name'] + , $_POST['create_pkg_url'] + , $_POST['create_pkg_git_url'] + , $_POST['create_pkg_desc']]); + echo 'Your package has been created.'; + } catch (Exception $e) { + echo '

' . $e->getMessage() . '.

'; + } + } +} +?> + +

Packages

+No packages found.

'; +else : +?> + + + + + + + + + + + + "; + + if ($pkg->url != '') + echo ""; + else + echo ""; + + echo ""; + + echo + " + "; + } + ?> +
NameAuthorDescriptionWebsiteVersions
{$pkg->name}" . $pkg->getAuthor()->name . "{$pkg->desc}Go&endash;"; + foreach ($pkg->getVersions() as $v) + echo "{$v->major}.{$v->minor}.{$v->revision} ({$v->time_added})"; + echo "{$pkg->name}
+ + +
+ +

Create an account

+You only need an account to add new packages. +
+ + + + + + + + + + + + + + + +
NamePosted publicly.
EmailKept private.
+
+ +
+ + +

Your packages

+ getPackages(); + if (count($pkgs) == 0) : + echo '

No packages found.

'; + else : + ?> +

To change details, please contact the CLPM maintainer.

+ + + + + + + + + "; + if ($pkg->url != '') + echo ""; + else + echo ""; + echo " + + + "; + } + ?> +
NameURLGit URLDescription
{$pkg->name}{$pkg->url}&endash;{$pkg->git_url}{$pkg->desc}
+ + +

Add new

+
+ + + + + + + + + + + + + + + + + + + + + + + + +
NameShown publicly.
URLShown publicly.
Git URL +

Your git repository. It should not require authorisation.

+

On scheduled intervals, we will clone the repository and + search for tags that look like a version number, e.g. v1.0.2. + For those versions that are not yet found in the database, + we will checkout the tag and run make TARGET.tar.gz for all targets + (currently linux32, linux64, mac, win32 and win64). + We will then copy tose files to /repo/PACKAGE/VERSION/PLATFORM.tar.gz.

+
Description + +
+
+ +

Login

+
+ + + + + + + + + + + + + +
Name
Password
+

If you lost your password, please contact the CLPM maintainer.

+
+prepare( - 'SELECT `package`.`id`,`package`.`name`,`url`,`desc`,`author`.`name` ' . - 'FROM `package`,`author` ' . - 'WHERE `package`.`author_id` = `author`.`id`'); -$stmt->execute(); -$stmt->bind_result($id, $name, $url, $desc, $author); -while ($stmt->fetch() === true) { - $repo[] = [ - 'id' => $id, - 'name' => $name, - 'author' => $author, - 'desc' => $desc, - 'url' => $url, +foreach (Package::search($_pdo) as $pkg) { + $new_pkg = [ + 'name' => $pkg->name, + 'author' => $pkg->getAuthor()->name, + 'desc' => $pkg->desc, + 'url' => $pkg->url, 'versions' => [] ]; -} -$stmt->close(); -$stmt = $db->prepare( - 'SELECT `major`,`minor`,`revision`,`depends` ' . - 'FROM `version` WHERE `package_id`=?'); -$stmt->bind_param('i', $pkg); -$stmt->bind_result($maj, $min, $rev, $deps); -for ($i = 0; $i < count($repo); $i++) { - $pkg = $repo[$i]['id']; - $stmt->execute(); - while ($stmt->fetch() === true) { - $repo[$i]['versions'][] = [ - 'version' => [$maj, $min, $rev], - 'depends' => json_decode($deps) + foreach ($pkg->getVersions() as $version) { + $new_pkg['version'][] = [ + 'version' => [$version->major, $version->minor, $version->revision], + 'depends' => json_decode($version->depends) ]; } - unset($repo[$i]['id']); + + $repo[] = $new_pkg; } print(json_encode($repo)); -- cgit v1.2.3