diff --git a/.gitignore b/.gitignore
index c0ad1d9..efd9f46 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,3 +21,4 @@ yarn-error.log*
*.sln
*.sw*
/data/stilus.log
+/coverage.clover
diff --git a/.stilus.yml b/.stilus.yml
index 93c28f3..80bd45b 100644
--- a/.stilus.yml
+++ b/.stilus.yml
@@ -15,6 +15,7 @@ api:
log_file: "./data/stilus.log"
dashboard:
+ language: en_EN
web_server:
host: "0.0.0.0"
port: 8090
diff --git a/composer.json b/composer.json
index 0c97d2c..8e39062 100644
--- a/composer.json
+++ b/composer.json
@@ -1,6 +1,5 @@
{
"name": "igniphp/stilus",
- "version": "0.0.1",
"description": "",
"keywords": [],
"license": "BSD-3-Clause",
@@ -13,22 +12,48 @@
"require": {
"php": ">=7.1.0",
"igniphp/framework": "^2.0",
- "igniphp/storage": "^0.4.2",
+ "igniphp/storage": "^0.6.0",
"igniphp/validation": "^1.1.0",
"symfony/yaml": "^4.1",
"zendframework/zend-mail": "^2.10",
- "zendframework/zend-crypt": "^3.3"
+ "zendframework/zend-crypt": "^3.3",
+ "league/flysystem": "^1.0",
+ "zircote/swagger-php": "^3.0"
},
"scripts": {
- "start": "php src/api/Stilus.php",
- "stop": "kill $(cat ./data/stilus.pid)"
+ "post-install-cmd": [
+
+ ],
+ "post-update-cmd": [],
+ "migrate": [
+ "Stilus\\Kernel\\Migration\\MigrationCommand::synchronize"
+ ],
+ "ci": [
+ "composer validate --no-check-all --strict",
+ "@phpcs",
+ "@test-coverage"
+ ],
+ "start": "php src/Stilus.php",
+ "stop": "kill $(cat ./data/stilus.pid)",
+ "phpcs": "phpcs --standard=PSR2 src",
+ "test": "phpunit",
+ "test-coverage": "phpunit --colors=always --coverage-clover clover.xml"
+ },
+ "scripts-descriptions": {
+ "phpcs": "Checks that the application code conforms to coding standard",
+ "test-coverage": "Launches the preconfigured PHPUnit with coverage",
+ "ci": "Continues integration checks",
+ "migrate": "Runs migrations, example usage: composer migration 1.0.0"
},
"require-dev": {
"phpunit/phpunit": ">=5.7.0",
- "mockery/mockery": ">=0.9.4",
- "phpunit/php-code-coverage": ">=4.0.0"
+ "phpunit/php-code-coverage": ">=4.0.0",
+ "fzaninotto/faker": "^1.8"
},
"autoload": {
+ "exclude-from-classmap": [
+ "src/api/Stilus.php"
+ ],
"psr-4": {
"Stilus\\": "src/api/"
}
diff --git a/composer.lock b/composer.lock
index 26a09fd..3c22999 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "4a32763295390bfef6319ecbfa848c3e",
+ "content-hash": "29fad14a5be7546f891c0ebf94758a92",
"packages": [
{
"name": "cache/adapter-common",
@@ -843,16 +843,16 @@
},
{
"name": "igniphp/storage",
- "version": "0.4.2",
+ "version": "0.6.0",
"source": {
"type": "git",
"url": "https://github.com/igniphp/storage.git",
- "reference": "475b44b9b530a1737f4293b0c3cd3bb9fca99334"
+ "reference": "dbfa7f0a29a67f02b113efe448346f195180bcf8"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/igniphp/storage/zipball/475b44b9b530a1737f4293b0c3cd3bb9fca99334",
- "reference": "475b44b9b530a1737f4293b0c3cd3bb9fca99334",
+ "url": "https://api.github.com/repos/igniphp/storage/zipball/dbfa7f0a29a67f02b113efe448346f195180bcf8",
+ "reference": "dbfa7f0a29a67f02b113efe448346f195180bcf8",
"shasum": ""
},
"require": {
@@ -911,7 +911,7 @@
"sqlite",
"unit of work"
],
- "time": "2018-07-16T07:58:44+00:00"
+ "time": "2018-09-27T06:35:43+00:00"
},
{
"name": "igniphp/uuid",
@@ -1002,6 +1002,90 @@
],
"time": "2018-03-20T17:20:48+00:00"
},
+ {
+ "name": "league/flysystem",
+ "version": "1.0.47",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/thephpleague/flysystem.git",
+ "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/a11e4a75f256bdacf99d20780ce42d3b8272975c",
+ "reference": "a11e4a75f256bdacf99d20780ce42d3b8272975c",
+ "shasum": ""
+ },
+ "require": {
+ "ext-fileinfo": "*",
+ "php": ">=5.5.9"
+ },
+ "conflict": {
+ "league/flysystem-sftp": "<1.0.6"
+ },
+ "require-dev": {
+ "phpspec/phpspec": "^3.4",
+ "phpunit/phpunit": "^5.7.10"
+ },
+ "suggest": {
+ "ext-fileinfo": "Required for MimeType",
+ "ext-ftp": "Allows you to use FTP server storage",
+ "ext-openssl": "Allows you to use FTPS server storage",
+ "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2",
+ "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3",
+ "league/flysystem-azure": "Allows you to use Windows Azure Blob storage",
+ "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching",
+ "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem",
+ "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files",
+ "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib",
+ "league/flysystem-webdav": "Allows you to use WebDAV storage",
+ "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter",
+ "spatie/flysystem-dropbox": "Allows you to use Dropbox storage",
+ "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "League\\Flysystem\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Frank de Jonge",
+ "email": "info@frenky.net"
+ }
+ ],
+ "description": "Filesystem abstraction: Many filesystems, one API.",
+ "keywords": [
+ "Cloud Files",
+ "WebDAV",
+ "abstraction",
+ "aws",
+ "cloud",
+ "copy.com",
+ "dropbox",
+ "file systems",
+ "files",
+ "filesystem",
+ "filesystems",
+ "ftp",
+ "rackspace",
+ "remote",
+ "s3",
+ "sftp",
+ "storage"
+ ],
+ "time": "2018-09-14T15:30:29+00:00"
+ },
{
"name": "paragonie/random_compat",
"version": "v2.0.17",
@@ -1449,6 +1533,55 @@
],
"time": "2017-10-23T01:57:42+00:00"
},
+ {
+ "name": "symfony/finder",
+ "version": "v4.1.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/finder.git",
+ "reference": "1f17195b44543017a9c9b2d437c670627e96ad06"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/finder/zipball/1f17195b44543017a9c9b2d437c670627e96ad06",
+ "reference": "1f17195b44543017a9c9b2d437c670627e96ad06",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "4.1-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Finder\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Finder Component",
+ "homepage": "https://symfony.com",
+ "time": "2018-10-03T08:47:56+00:00"
+ },
{
"name": "symfony/polyfill-ctype",
"version": "v1.9.0",
@@ -2243,6 +2376,69 @@
"zf2"
],
"time": "2018-02-01T17:05:33+00:00"
+ },
+ {
+ "name": "zircote/swagger-php",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/zircote/swagger-php.git",
+ "reference": "8fc3bc059a7f71b3f100bcfd84a96b5b8fcf6fcf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/zircote/swagger-php/zipball/8fc3bc059a7f71b3f100bcfd84a96b5b8fcf6fcf",
+ "reference": "8fc3bc059a7f71b3f100bcfd84a96b5b8fcf6fcf",
+ "shasum": ""
+ },
+ "require": {
+ "doctrine/annotations": "*",
+ "php": ">=7.0",
+ "symfony/finder": ">=2.2",
+ "symfony/yaml": ">=2.8"
+ },
+ "require-dev": {
+ "phpunit/phpunit": ">=6.3",
+ "squizlabs/php_codesniffer": ">=3.3",
+ "zendframework/zend-form": "<2.8"
+ },
+ "bin": [
+ "bin/openapi"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "OpenApi\\": "src"
+ },
+ "files": [
+ "src/functions.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Robert Allen",
+ "email": "zircote@gmail.com",
+ "homepage": "http://www.zircote.com"
+ },
+ {
+ "name": "Bob Fanger",
+ "email": "bfanger@gmail.com",
+ "homepage": "http://bfanger.nl"
+ }
+ ],
+ "description": "swagger-php - Generate interactive documentation for your RESTful API using phpdoc annotations",
+ "homepage": "https://github.com/zircote/swagger-php/",
+ "keywords": [
+ "api",
+ "json",
+ "rest",
+ "service discovery"
+ ],
+ "time": "2018-09-30T12:19:07+00:00"
}
],
"packages-dev": [
@@ -2301,118 +2497,54 @@
"time": "2017-07-22T11:58:36+00:00"
},
{
- "name": "hamcrest/hamcrest-php",
- "version": "v2.0.0",
+ "name": "fzaninotto/faker",
+ "version": "v1.8.0",
"source": {
"type": "git",
- "url": "https://github.com/hamcrest/hamcrest-php.git",
- "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad"
+ "url": "https://github.com/fzaninotto/Faker.git",
+ "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/776503d3a8e85d4f9a1148614f95b7a608b046ad",
- "reference": "776503d3a8e85d4f9a1148614f95b7a608b046ad",
+ "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/f72816b43e74063c8b10357394b6bba8cb1c10de",
+ "reference": "f72816b43e74063c8b10357394b6bba8cb1c10de",
"shasum": ""
},
"require": {
- "php": "^5.3|^7.0"
- },
- "replace": {
- "cordoval/hamcrest-php": "*",
- "davedevelopment/hamcrest-php": "*",
- "kodova/hamcrest-php": "*"
- },
- "require-dev": {
- "phpunit/php-file-iterator": "1.3.3",
- "phpunit/phpunit": "~4.0",
- "satooshi/php-coveralls": "^1.0"
- },
- "type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0-dev"
- }
- },
- "autoload": {
- "classmap": [
- "hamcrest"
- ]
- },
- "notification-url": "https://packagist.org/downloads/",
- "license": [
- "BSD"
- ],
- "description": "This is the PHP port of Hamcrest Matchers",
- "keywords": [
- "test"
- ],
- "time": "2016-01-20T08:20:44+00:00"
- },
- {
- "name": "mockery/mockery",
- "version": "1.1.0",
- "source": {
- "type": "git",
- "url": "https://github.com/mockery/mockery.git",
- "reference": "99e29d3596b16dabe4982548527d5ddf90232e99"
- },
- "dist": {
- "type": "zip",
- "url": "https://api.github.com/repos/mockery/mockery/zipball/99e29d3596b16dabe4982548527d5ddf90232e99",
- "reference": "99e29d3596b16dabe4982548527d5ddf90232e99",
- "shasum": ""
- },
- "require": {
- "hamcrest/hamcrest-php": "~2.0",
- "lib-pcre": ">=7.0",
- "php": ">=5.6.0"
+ "php": "^5.3.3 || ^7.0"
},
"require-dev": {
- "phpdocumentor/phpdocumentor": "^2.9",
- "phpunit/phpunit": "~5.7.10|~6.5"
+ "ext-intl": "*",
+ "phpunit/phpunit": "^4.8.35 || ^5.7",
+ "squizlabs/php_codesniffer": "^1.5"
},
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "1.0.x-dev"
+ "dev-master": "1.8-dev"
}
},
"autoload": {
- "psr-0": {
- "Mockery": "library/"
+ "psr-4": {
+ "Faker\\": "src/Faker/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
- "BSD-3-Clause"
+ "MIT"
],
"authors": [
{
- "name": "Pádraic Brady",
- "email": "padraic.brady@gmail.com",
- "homepage": "http://blog.astrumfutura.com"
- },
- {
- "name": "Dave Marshall",
- "email": "dave.marshall@atstsolutions.co.uk",
- "homepage": "http://davedevelopment.co.uk"
+ "name": "François Zaninotto"
}
],
- "description": "Mockery is a simple yet flexible PHP mock object framework",
- "homepage": "https://github.com/mockery/mockery",
+ "description": "Faker is a PHP library that generates fake data for you.",
"keywords": [
- "BDD",
- "TDD",
- "library",
- "mock",
- "mock objects",
- "mockery",
- "stub",
- "test",
- "test double",
- "testing"
- ],
- "time": "2018-05-08T08:54:48+00:00"
+ "data",
+ "faker",
+ "fixtures"
+ ],
+ "time": "2018-07-12T10:23:15+00:00"
},
{
"name": "myclabs/deep-copy",
diff --git a/logo/stilus.svg b/logo/stilus.svg
deleted file mode 100644
index edf060d..0000000
--- a/logo/stilus.svg
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
diff --git a/phpunit.xml b/phpunit.xml
index e7c9331..fc8cc21 100755
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -7,7 +7,7 @@
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
- bootstrap="tests/bootstrap.php"
+ bootstrap="tests/api/bootstrap.php"
>
diff --git a/src/api/Exception/BootException.php b/src/api/Exception/BootException.php
index 72ff5cb..f662884 100644
--- a/src/api/Exception/BootException.php
+++ b/src/api/Exception/BootException.php
@@ -8,29 +8,30 @@ class BootException extends RuntimeException
{
public static function forInvalidPHPVersion(string $currentVersion): self
{
- return self::withMessage("Stilus requires PHP 7.1.0 or higher, you are running PHP {$currentVersion}.");
+ return new self("Stilus requires PHP 7.1.0 or higher, you are running PHP {$currentVersion}.");
}
public static function forMissingComposer(): self
{
- return self::withMessage('`vendor` dir is missing. Did you forgot to run `composer install?`');
+ return new self('`vendor` dir is missing. Did you forgot to run `composer install?`');
}
public static function forMissingBaseConfiguration(): self
{
- return self::withMessage('`.stilus.yml` file is missing. Did you deleted it by accident?');
+ return new self('`.stilus.yml` file is missing. Did you deleted it by accident?');
}
public static function forInvalidBaseConfiguration(Throwable $previous): self
{
- return self::withPrevious(
+ return new self(
'There was a problem with parsing `.stilus.yml` file. Please check the config file.',
+ $previous->getCode(),
$previous
);
}
public static function forMissingConfigurationOption(string $name): self
{
- return self::withMessage("`{$name}`` configuration option is missing.");
+ return new self("`{$name}`` configuration option is missing.");
}
}
diff --git a/src/api/Exception/DomainException.php b/src/api/Exception/DomainException.php
new file mode 100644
index 0000000..ab741b5
--- /dev/null
+++ b/src/api/Exception/DomainException.php
@@ -0,0 +1,7 @@
+getCode(), $previous);
- }
-}
diff --git a/src/api/Exception/RuntimeException.php b/src/api/Exception/RuntimeException.php
index 1a9b9ac..3c977d6 100644
--- a/src/api/Exception/RuntimeException.php
+++ b/src/api/Exception/RuntimeException.php
@@ -2,7 +2,8 @@
namespace Stilus\Exception;
-class RuntimeException extends \RuntimeException implements StilusException
+use RuntimeException as PhpRuntimeException;
+
+class RuntimeException extends PhpRuntimeException implements StilusException
{
- use ExceptionTrait;
}
diff --git a/src/api/Kernel/Installer.php b/src/api/Kernel/Installer.php
new file mode 100644
index 0000000..de16fd5
--- /dev/null
+++ b/src/api/Kernel/Installer.php
@@ -0,0 +1,25 @@
+createServiceLocator();
+ $connection = $system->createDatabaseConnection();
+ $container->set(Connection::class, $connection);
+
+ $versionSynchronizer = new VersionSynchronizer($connection);
+ $migrationManager = new MigrationManager($versionSynchronizer);
+ $container->set(MigrationManager::class, $migrationManager);
+
+ self::loadModules($container);
+ $arguments = $event->getArguments();
+
+ if (isset($arguments[0])) {
+ $migrationManager->migrate(Version::fromString($arguments[0]));
+ } else {
+ $migrationManager->migrate();
+ }
+ }
+
+ private static function loadModules(ServiceLocator $locator): void
+ {
+ $modules = [];
+
+ foreach (System::BASE_MODULES as $module) {
+ if (!class_exists($module)) {
+ continue;
+ }
+ $modules[] = new $module;
+ }
+
+ foreach ($modules as $module) {
+ if ($module instanceof ConfigProvider) {
+ $module->provideConfig($locator->get(Config::class));
+ }
+ }
+
+ foreach ($modules as $module) {
+ if ($module instanceof ServiceProvider) {
+ $module->provideServices($locator);
+ }
+ }
+ }
+}
diff --git a/src/api/Kernel/Migration/VersionSynchronizer.php b/src/api/Kernel/Migration/VersionSynchronizer.php
new file mode 100644
index 0000000..46e4251
--- /dev/null
+++ b/src/api/Kernel/Migration/VersionSynchronizer.php
@@ -0,0 +1,75 @@
+connection = $connection;
+
+ $this->prepareMigrationTable();
+ }
+
+ private function prepareMigrationTable(): void
+ {
+ $cursor = $this->connection->createCursor(
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='migrations'"
+ );
+
+ $tableExists = $cursor->current();
+
+ if (!$tableExists) {
+ $this->createMigrationTable();
+ }
+ }
+
+ private function createMigrationTable(): void
+ {
+ $cursor = $this->connection->createCursor(
+ "CREATE TABLE migrations (
+ major INTEGER NOT NULL DEFAULT 0,
+ minor INTEGER NOT NULL DEFAULT 0,
+ patch INTEGER NOT NULL DEFAULT 0
+ )"
+ );
+
+ $cursor->execute();
+ }
+
+ public function getVersion(): Version
+ {
+ $cursor = $this->connection->createCursor(
+ 'SELECT major, minor, patch FROM migrations ORDER BY major DESC, minor DESC, patch DESC'
+ );
+
+ $current = $cursor->current();
+ if ($current === null) {
+ $current = Version::fromString('0.0.0');
+ } else {
+ $current = Version::fromString(implode('.', $current));
+ }
+
+ return $current;
+ }
+
+ public function setVersion(Version $version): void
+ {
+ $cursor = $this->connection->createCursor(
+ 'INSERT INTO migrations (major, minor, patch) VALUES (:major, :minor, :patch)',
+ [
+ $version->getMajor(),
+ $version->getMinor(),
+ $version->getPatch()
+ ]
+ );
+
+ $cursor->execute();
+ }
+}
diff --git a/src/api/Kernel/Module.php b/src/api/Kernel/Module.php
new file mode 100644
index 0000000..83e1fe1
--- /dev/null
+++ b/src/api/Kernel/Module.php
@@ -0,0 +1,10 @@
+')) {
+ throw BootException::forInvalidPHPVersion(PHP_VERSION);
+ }
+
+ if (!file_exists(self::VENDOR_AUTOLOADER)) {
+ throw BootException::forMissingComposer();
+ }
+
+ require_once self::VENDOR_AUTOLOADER;
+ }
+
+ public function createDatabaseConnection(): Connection
+ {
+ if (!ConnectionManager::has('default')) {
+ ConnectionManager::register('default', new Connection('sqlite:' . self::DB_PATH));
+ }
+
+ return ConnectionManager::get('default');
+ }
+
+ public function getBaseConfig(): Config
+ {
+ if ($this->config instanceof Config) {
+ return $this->config;
+ }
+
+ $config = $this->loadBaseConfig();
+ return $this->config = new Config([
+ 'dir.basedir' => System::DIR,
+ 'dir.config' => realpath(System::DIR . DIRECTORY_SEPARATOR . $config['paths']['config']),
+ 'dir.database' => realpath(System::DIR . DIRECTORY_SEPARATOR . $config['paths']['database']),
+ 'dir.themes' => realpath(System::DIR . DIRECTORY_SEPARATOR . $config['paths']['themes']),
+ ]);
+ }
+
+ private function loadBaseConfig(): array
+ {
+ if (!is_readable(self::BASE_CONFIG)) {
+ throw BootException::forMissingBaseConfiguration();
+ }
+
+ try {
+ return $configuration = Yaml::parseFile(self::BASE_CONFIG);
+ } catch (Throwable $throwable) {
+ throw BootException::forInvalidBaseConfiguration($throwable);
+ }
+ }
+
+ public function createServiceLocator(): ServiceLocator
+ {
+ if (!$this->container instanceof ContainerInterface) {
+ $this->container = new ServiceLocator();
+ $this->container->set(Config::class, $this->getBaseConfig());
+ }
+
+ return $this->container;
+ }
+}
diff --git a/src/api/Platform/Controller/CreatePlatform.php b/src/api/Platform/Controller/CreatePlatform.php
deleted file mode 100644
index 1ac2d01..0000000
--- a/src/api/Platform/Controller/CreatePlatform.php
+++ /dev/null
@@ -1,21 +0,0 @@
-platformService = $platformService;
}
public function __invoke(ServerRequestInterface $request): ResponseInterface
diff --git a/src/api/Platform/Controller/InstallPlatform.php b/src/api/Platform/Controller/InstallPlatform.php
new file mode 100644
index 0000000..015705e
--- /dev/null
+++ b/src/api/Platform/Controller/InstallPlatform.php
@@ -0,0 +1,48 @@
+platformService = $platformService;
+ }
+
+ public function __invoke(ServerRequestInterface $request): ResponseInterface
+ {
+ $this->platformService->install();
+ }
+
+ public static function getRoute(): Route
+ {
+ return Route::post('/platform');
+ }
+}
diff --git a/src/api/Platform/Exception/PlartformException.php b/src/api/Platform/Exception/PlartformException.php
new file mode 100644
index 0000000..f9ed1ce
--- /dev/null
+++ b/src/api/Platform/Exception/PlartformException.php
@@ -0,0 +1,9 @@
+id = new Uuid();
+ $this->email = $email;
+ $this->createPassword($password);
+ $this->validate();
+ }
+
+ public function getId(): Id
+ {
+ return $this->id;
+ }
+
+ public function getEmail(): string
+ {
+ return $this->email;
+ }
+
+ public function validatePassword(string $password): bool
+ {
+ return password_verify($password, $this->password);
+ }
+
+ public function createPassword(string $password): void
+ {
+ $this->password = password_hash($password, PASSWORD_BCRYPT);
+ }
+
+ public function changePassword(string $oldPassword, string $newPassword): bool
+ {
+ if (!$this->validatePassword($oldPassword)) {
+ return false;
+ }
+
+ $this->createPassword($newPassword);
+
+ return true;
+ }
+
+ private function validate()
+ {
+ if (!Constraint::email()->validate($this->email)) {
+ throw UserException::forCreationFailure();
+ }
+ }
+}
diff --git a/src/api/Platform/Persistence/UserRepository.php b/src/api/Platform/Persistence/UserRepository.php
new file mode 100644
index 0000000..514e165
--- /dev/null
+++ b/src/api/Platform/Persistence/UserRepository.php
@@ -0,0 +1,33 @@
+query(
+ 'SELECT * FROM users WHERE email = :email LIMIT 1',
+ [
+ 'email' => $email,
+ ]
+ );
+ $cursor->hydrateWith($this->hydrator);
+ $user = $cursor->current();
+ $cursor->close();
+
+ if ($user === null) {
+ throw UserException::forNotFound();
+ }
+
+ return $user;
+ }
+
+ public static function getEntityClass(): string
+ {
+ return User::class;
+ }
+}
diff --git a/src/api/Platform/Persistence/UserSchema.php b/src/api/Platform/Persistence/UserSchema.php
new file mode 100644
index 0000000..27c4a4e
--- /dev/null
+++ b/src/api/Platform/Persistence/UserSchema.php
@@ -0,0 +1,46 @@
+connection = $connection;
+ }
+
+ public function up(): void
+ {
+ $this->connection
+ ->createCursor('CREATE TABLE IF NOT EXISTS "users" (
+ "id" char(22) PRIMARY KEY NOT NULL,
+ "email" char(128) NOT NULL,
+ "password" char(128) NOT NULL
+ )')
+ ->execute();
+ }
+
+ public function down(): void
+ {
+ $this->connection
+ ->createCursor('DROP TABLE IF EXISTS "users"')
+ ->execute();
+ }
+
+ public function getVersion(): Version
+ {
+ return Version::fromString('1.0.0');
+ }
+
+ public static function factory(ContainerInterface $container): self
+ {
+ return new self($container->get(Connection::class));
+ }
+}
diff --git a/src/api/Platform/PlatformModule.php b/src/api/Platform/PlatformModule.php
index 779ee75..2028c7c 100644
--- a/src/api/Platform/PlatformModule.php
+++ b/src/api/Platform/PlatformModule.php
@@ -7,14 +7,15 @@
use Igni\Application\Providers\ServiceProvider;
use Igni\Container\ServiceLocator;
use Psr\Container\ContainerInterface;
-use Stilus\Platform\Controller\CreatePlatform;
+use Stilus\Kernel\Module;
+use Stilus\Platform\Controller\InstallPlatform;
use Stilus\Platform\Controller\GetPlatformStatus;
-class PlatformModule implements ControllerProvider, ServiceProvider
+class PlatformModule implements ControllerProvider, ServiceProvider, Module
{
public function provideControllers(ControllerAggregator $controllers): void
{
- $controllers->register(CreatePlatform::class);
+ $controllers->register(InstallPlatform::class);
$controllers->register(GetPlatformStatus::class);
}
@@ -25,4 +26,9 @@ public function provideServices(ContainerInterface $container): void
{
$container->share(PlatformService::class);
}
+
+ public static function install(ContainerInterface $container)
+ {
+
+ }
}
diff --git a/src/api/Platform/PlatformService.php b/src/api/Platform/PlatformService.php
index 85d83a2..ca8161b 100644
--- a/src/api/Platform/PlatformService.php
+++ b/src/api/Platform/PlatformService.php
@@ -16,6 +16,27 @@ public function __construct(Config $paths)
$this->config = $paths;
}
+ public function setLanguage(): void
+ {
+
+ }
+
+ public function setupDatabase(): void
+ {
+
+ }
+
+ public function createAdmin(string $email, string $password): void
+ {
+
+ }
+
+ public function postInstall(): void
+ {
+
+ }
+
+
public function getStatus()
{
diff --git a/src/api/Stilus.php b/src/api/Stilus.php
index 8177778..fb54c2b 100644
--- a/src/api/Stilus.php
+++ b/src/api/Stilus.php
@@ -1,57 +1,41 @@
')) {
- throw BootException::forInvalidPHPVersion(PHP_VERSION);
+use Stilus\Kernel\System;
+use Igni\Storage\Driver\Connection;
+use OpenApi\Annotations as Doc;
+
+// Composer is autoloading this file even when test are run, this hack stops from
+// excecution while tests are runnnig
+if (defined('STILUS_TEST')) {
+ return;
}
-const STILUS_MODULES = [
- PlatformModule::class
-];
+const STILUS_VERSION = "1.0.0";
-const STILUS_DIR = __DIR__ . '/../..';
-
-const STILUS_BASE_CONFIG = STILUS_DIR . '/.stilus.yml';
-
-const STILUS_VENDOR_DIR = __DIR__ . '/../../vendor';
-
-const STILUS_VENDOR_AUTOLOADER = __DIR__ . '/../../vendor/autoload.php';
+/**
+ * @Doc\Info(
+ * version=STILUS_VERSION,
+ * title="Stilus API",
+ * @Doc\License(
+ * name="BSD-3-Clause",
+ * url="https://www.opensource.org/licenses/BSD-3-Clause"
+ * )
+ * )
+ */
+$system = new System();
// Bootstrap
-(new class {
-
- private function setupAutoload(): void
- {
- if (!file_exists(STILUS_VENDOR_AUTOLOADER)) {
- throw BootException::forMissingComposer();
- }
+(new class($system) {
- require STILUS_VENDOR_AUTOLOADER;
- }
+ private $system;
- private function loadBootstrapConfig(): array
+ public function __construct(System $system)
{
- if (!is_readable(STILUS_BASE_CONFIG)) {
- throw BootException::forMissingBaseConfiguration();
- }
-
- try {
- return $configuration = Yaml::parseFile(STILUS_BASE_CONFIG);
- } catch (Throwable $throwable) {
- throw BootException::forInvalidBaseConfiguration($throwable);
- }
+ $this->system = $system;
}
private function setupServer(array $config): HttpServer
@@ -86,26 +70,22 @@ private function setupServer(array $config): HttpServer
public function main(): void
{
- $this->setupAutoload();
-
- $config = $this->loadBootstrapConfig();
- $container = new ServiceLocator();
- $container->share(Config::class, function() use ($config) {
- return new Config([
- 'dir.basedir', STILUS_DIR,
- 'dir.config' => realpath(STILUS_DIR . DIRECTORY_SEPARATOR . $config['paths']['config']),
- 'dir.database', realpath(STILUS_DIR . DIRECTORY_SEPARATOR . $config['paths']['database']),
- 'dir.themes', realpath(STILUS_DIR . DIRECTORY_SEPARATOR . $config['paths']['themes']),
- ]);
- });
- $application = new HttpApplication($container);
-
- foreach (STILUS_MODULES as $module) {
+ $config = $this->system->getBaseConfig();
+ $connection = $this->system->createDatabaseConnection();
+
+ $serviceLocator = $this->system->createServiceLocator();
+ $serviceLocator->set(Connection::class, $connection);
+ $application = new HttpApplication($serviceLocator);
+
+ foreach (System::STILUS_MODULES as $module) {
$application->extend($module);
}
$server = null;
- if (isset($config['api']) &&
+
+ // Server should be only available in sapi mode and when proper config is set
+ if (php_sapi_name() == "cli" &&
+ isset($config['api']) &&
isset($config['api']['http_server']) &&
isset($config['api']['http_server']['enable']) &&
$config['api']['http_server']['enable']
diff --git a/src/dashboard/i18n/en_EN.json b/src/dashboard/i18n/en_EN.json
new file mode 100644
index 0000000..0db3279
--- /dev/null
+++ b/src/dashboard/i18n/en_EN.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/tests/api/Fixtures/.gitkeep b/tests/api/Fixtures/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/tests/api/Fixtures/test.db b/tests/api/Fixtures/test.db
new file mode 100644
index 0000000..8b17d50
Binary files /dev/null and b/tests/api/Fixtures/test.db differ
diff --git a/tests/api/Functional/Kernel/Migration/MigrationServiceTest.php b/tests/api/Functional/Kernel/Migration/MigrationServiceTest.php
new file mode 100644
index 0000000..440a30b
--- /dev/null
+++ b/tests/api/Functional/Kernel/Migration/MigrationServiceTest.php
@@ -0,0 +1,10 @@
+createConnection();
+ $this->connection->createCursor('DROP TABLE IF EXISTS migrations')->execute();
+ parent::setUp();
+ }
+
+ public function testCanInstantiate(): void
+ {
+ $synchronizer = new VersionSynchronizer($this->connection);
+ self::assertInstanceOf(VersionSynchronizer::class, $synchronizer);
+ }
+
+ public function testSetAndGetVersion(): void
+ {
+ $synchronizer = new VersionSynchronizer($this->connection);
+ $synchronizer->setVersion(Version::fromString('1.0.0'));
+ $synchronizer->setVersion(Version::fromString('1.2.0'));
+
+ $synchronizer = new VersionSynchronizer($this->connection);
+
+ self::assertTrue($synchronizer->getVersion()->equalsLiteral('1.2.0'));
+
+ $cursor = $this->connection->createCursor('SELECT *FROM migrations');
+ self::assertCount(2, $cursor->toArray());
+ }
+}
diff --git a/tests/api/Functional/Platform/UserRepositoryTest.php b/tests/api/Functional/Platform/UserRepositoryTest.php
new file mode 100644
index 0000000..b828127
--- /dev/null
+++ b/tests/api/Functional/Platform/UserRepositoryTest.php
@@ -0,0 +1,69 @@
+createConnection();
+ $this->faker = FakerFactory::create();
+ parent::setUp();
+ }
+
+ public function testFindByEmail(): void
+ {
+ $repository = $this->getUserRepository();
+ $this->createTestUsers($repository);
+
+ $email = 'test@user.com';
+ $repository->create(new User($email, 'test'));
+
+ $user = $repository->findUserByEmail($email);
+ self::assertInstanceOf(User::class, $user);
+ self::assertSame($email, $user->getEmail());
+ self::assertTrue($user->validatePassword('test'));
+ }
+
+ public function testFailFindByEmail(): void
+ {
+ $this->expectException(EntityNotFound::class);
+ $repository = $this->getUserRepository();
+ $this->createTestUsers($repository);
+
+ $repository->findUserByEmail('test');
+ }
+
+ private function getUserRepository(): UserRepository
+ {
+ $entityManager = new EntityManager();
+ $repository = new UserRepository($entityManager, $this->connection);
+ $schema = new UserSchema($this->connection);
+ $schema->down();
+ $schema->up();
+ return $repository;
+ }
+
+ private function createTestUsers(UserRepository $repository, int $amount = 5)
+ {
+ for ($i = 0; $i < $amount; $i++) {
+ $user = new User($this->faker->email, $this->faker->password);
+ $repository->create($user);
+ }
+ }
+}
diff --git a/tests/api/StorageTestTrait.php b/tests/api/StorageTestTrait.php
new file mode 100644
index 0000000..a747e69
--- /dev/null
+++ b/tests/api/StorageTestTrait.php
@@ -0,0 +1,42 @@
+connection = ConnectionManager::get($name);
+ }
+
+ $this->connection = new Connection('sqlite:' . STILUS_TEST_FIXTURE_DIR . '/test.db');
+ ConnectionManager::register($name, $this->connection);
+
+ return $this->connection;
+ }
+
+ public function createEntityManager(): EntityManager
+ {
+ $this->entityManager = new EntityManager();
+
+ return $this->entityManager;
+ }
+
+ public function createStorage(): Storage
+ {
+ $this->storage = new Storage($this->entityManager ?? $this->createEntityManager());
+ }
+}
diff --git a/tests/api/Unit/Platform/UserTest.php b/tests/api/Unit/Platform/UserTest.php
new file mode 100644
index 0000000..eb97c84
--- /dev/null
+++ b/tests/api/Unit/Platform/UserTest.php
@@ -0,0 +1,37 @@
+expectException(UserException::class);
+ $this->expectExceptionCode(ExceptionCode::INVALID_USER_EMAIL);
+ new User('invalidemail', 'aa');
+ }
+
+ public function testCreatePassword(): void
+ {
+ $user = new User('test@email.com', 'password');
+ self::assertTrue($user->validatePassword('password'));
+ }
+
+ public function testVerifyPassword(): void
+ {
+ $user = new User('test@email.com', 'password');
+ self::assertTrue($user->validatePassword('password'));
+ self::assertFalse($user->validatePassword('invalid'));
+ self::assertFalse($user->validatePassword('error'));
+ }
+}
diff --git a/tests/api/bootstrap.php b/tests/api/bootstrap.php
index 58ce587..09b24b2 100644
--- a/tests/api/bootstrap.php
+++ b/tests/api/bootstrap.php
@@ -1,4 +1,7 @@
-