diff --git a/composer.json b/composer.json
index 00f8aff3..41a9c3e5 100644
--- a/composer.json
+++ b/composer.json
@@ -39,7 +39,7 @@
"rakibtg/sleekdb": "^2.13",
"laminas/laminas-text": "^2.9",
"swagger-api/swagger-ui": "^4.14",
- "zircote/swagger-php": "^4.4",
+ "zircote/swagger-php": "~4.4",
"psr/simple-cache": "^1.0",
"vaibhavpandeyvpz/phemail": "^1.1"
diff --git a/src/Console/Commands/ModuleGenerateCommand.php b/src/Console/Commands/ModuleGenerateCommand.php
index 4f230166..819f1770 100644
--- a/src/Console/Commands/ModuleGenerateCommand.php
+++ b/src/Console/Commands/ModuleGenerateCommand.php
@@ -14,9 +14,10 @@
namespace Quantum\Console\Commands;
+use Quantum\Libraries\Module\ModuleManager;
use Quantum\Libraries\Storage\FileSystem;
use Quantum\Console\QtCommand;
-use Quantum\Di\Di;
+use Exception;
/**
* Class OpenApiUiAssetsCommand
@@ -24,7 +25,6 @@
*/
class ModuleGenerateCommand extends QtCommand
{
-
/**
* File System
* @var FileSystem
@@ -62,199 +62,29 @@ class ModuleGenerateCommand extends QtCommand
* @var array
*/
protected $options = [
- ['yes', 'y', 'none', 'Module enabled status']
- ];
-
- /**
- * Folder names
- * @var string[][]
- */
- protected $folders = [
- DS,
- DS . 'Controllers',
- DS . 'Models',
- DS . 'Config',
- DS . 'Views',
- DS . 'Views' . DS . 'layouts',
- DS . 'Views' . DS . 'partials',
+ ['yes', 'y', 'none', 'Module enabled status'],
+ ['template', 't', 'optional', 'The module template', 'web'],
+ ['demo', 'd', 'optional', 'Use demo template', 'no'],
];
/**
* Executes the command
- * @throws \Quantum\Exceptions\FileSystemException
- * @throws \Quantum\Exceptions\DiException
+ * @throws Exception
*/
public function exec()
{
- $this->fs = Di::get(FileSystem::class);
- $newModuleName = ucfirst($this->getArgument('module'));
+ try {
+ $moduleName = $this->getArgument('module');
- $modulesConfigPath = base_dir() . DS . 'shared' . DS . 'config' . DS . 'modules.php';
- $modules = require_once $modulesConfigPath;
+ $moduleManager = new ModuleManager($moduleName, $this->getOption('template'), $this->getOption('demo'), $this->getOption('yes'));
- foreach ($modules['modules'] as $module => $options) {
- if ($module == $newModuleName || $options['prefix'] == strtolower($newModuleName)) {
- $this->error('A module or prefix named ' . $newModuleName . ' already exists');
- return;
- }
- }
-
- $this->fs->put(
- $modulesConfigPath,
- str_replace(
- "'modules' => [",
- $this->addModuleConfig($newModuleName),
- $this->fs->get($modulesConfigPath)
- )
- );
+ $moduleManager->addModuleConfig();
- foreach ($this->folders as $folder) {
- $this->fs->makeDirectory(modules_dir() . DS . $newModuleName . $folder);
- }
-
- $files = [
- 'Controllers' . DS . 'MainController.php' => $this->controllerTemplate($newModuleName),
- 'Views' . DS . 'index.php' => $this->viewTemplate($newModuleName),
- 'Views' . DS . 'layouts' . DS . 'main.php' => $this->viewLayoutsTemplate(),
- 'Views' . DS . 'partials' . DS . 'bubbles.php' => $this->viewBubblesTemplate(),
- 'Config' . DS . 'routes.php' => $this->routesTemplate(),
- ];
+ $moduleManager->writeContents();
- foreach ($files as $file => $value) {
- $this->fs->put(modules_dir() . DS . $newModuleName . DS . $file, $value);
+ $this->info($moduleName . ' module resources successfully published');
+ } catch (Exception $e) {
+ $this->error($e->getMessage());
}
-
- $this->info($newModuleName . ' module resources successfully published');
- }
-
- /**
- * Add module to config
- * @param string $module
- * @return string
- */
- private function addModuleConfig(string $module): string
- {
- $enabled = $this->getOption('yes') ? "true" : "false";
-
- return "'modules' => [
- '" . $module . "' => [
- 'prefix' => '" . strtolower($module) . "',
- 'enabled' => " . $enabled . ",
- ],";
- }
-
- /**
- * Controller template
- * @param string $moduleName
- * @return string
- */
- private function controllerTemplate($moduleName)
- {
- return 'setLayout(\'layouts' . DS . 'main\');
- $view->setParams([
- \'title\' => config()->get(\'app_name\'),
- ]);
- $response->html($view->render(\'index\'));
- }
-};';
- }
-
- /**
- * View template
- * @param string $moduleName
- * @return string
- */
- private function viewTemplate($moduleName)
- {
- return '
-
-
-
-
-
-
' . strtoupper($moduleName) . ' HOME PAGE
-
-
-
-';
- }
-
- /**
- * View bubbles template
- * @param string $moduleName
- * @return string
- */
- private function viewBubblesTemplate()
- {
- return '';
- }
-
- /**
- * viewLayouts template
- * @return string
- */
- private function viewLayoutsTemplate()
- {
- return '
-
-
-
-
-
-
-
-
- url(\'css/materialize.min.css\') ?>\' type=\'text/css\' media=\'screen,projection\' />
- url(\'css/custom.css\') ?>\' type=\'text/css\' />
-
-
-
-
-
-
-
-
-
-
-';
- }
-
- /**
- * Routes template
- * @return string
- */
- private function routesTemplate()
- {
- return 'get(\'/\', \'MainController\', \'index\');
-};';
}
}
diff --git a/src/Libraries/Module/ModuleManager.php b/src/Libraries/Module/ModuleManager.php
new file mode 100644
index 00000000..ef86208e
--- /dev/null
+++ b/src/Libraries/Module/ModuleManager.php
@@ -0,0 +1,107 @@
+moduleName = $moduleName;
+
+ $this->template = $template;
+
+ $this->demo = $demo;
+
+ $this->optionEnabled = $enabled;
+
+ $type = $this->demo == "yes" ? "Demo" : "Default";
+
+ $this->templatePath = __DIR__ . DS . "Templates" . DS . $type . DS . ucfirst($this->template);
+
+ $this->modulePath = modules_dir() . DS . $this->moduleName;
+
+ $this->fs = Di::get(FileSystem::class);
+ }
+
+ public function writeContents()
+ {
+ if (!$this->fs->isDirectory(modules_dir())) {
+ $this->fs->makeDirectory(modules_dir());
+ }
+ $this->copyDirectoryWithTemplates($this->templatePath, $this->modulePath);
+ }
+
+ public function addModuleConfig()
+ {
+ $modulesConfigPath = base_dir() . DS . 'shared' . DS . 'config' . DS . 'modules.php';
+ $modules = require $modulesConfigPath;
+
+ foreach ($modules['modules'] as $module => $options) {
+ if ($module == $this->moduleName || $options['prefix'] == strtolower($this->moduleName)) {
+ throw new \Exception("A module or prefix named '$this->moduleName' already exists");
+ }
+ }
+
+ $this->fs->put(
+ $modulesConfigPath,
+ str_replace(
+ "'modules' => [",
+ $this->writeModuleConfig($this->moduleName),
+ $this->fs->get($modulesConfigPath)
+ )
+ );
+ }
+
+ private function copyDirectoryWithTemplates($src, $dst) {
+ if (!$this->fs->isDirectory($src)) {
+ throw new \Exception("Directory '$src' does not exist");
+ }
+
+ if (!$this->fs->isDirectory($dst)) {
+ $this->fs->makeDirectory($dst);
+ }
+
+ $dir = $this->fs->listDirectory($src);
+
+ foreach ($dir as $file) {
+ $srcPath = $file;
+ $dstPath = str_replace($src, $dst, $file);
+
+ if ($this->fs->isDirectory($srcPath)) {
+ $this->copyDirectoryWithTemplates($srcPath, $dstPath);
+ } else {
+ $processedContent = require_once $srcPath;
+ $this->fs->put($dstPath, $processedContent);
+ }
+ }
+ }
+
+ /**
+ * Add module to config
+ * @param string $module
+ * @return string
+ */
+ private function writeModuleConfig(string $module): string
+ {
+ $enabled = $this->optionEnabled ? "true" : "false";
+
+ $prefix = $this->template == "web" && $this->demo == "yes" ? "" : strtolower($module);
+
+ return "'modules' => [
+ '" . $module . "' => [
+ 'prefix' => '" . $prefix . "',
+ 'enabled' => " . $enabled . ",
+ ],";
+ }
+}
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Api/Config/cors.php b/src/Libraries/Module/Templates/Default/Api/Config/cors.php
new file mode 100644
index 00000000..fb67dbc5
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Api/Config/cors.php
@@ -0,0 +1,17 @@
+ \'*\',
+ \'Access-Control-Allow-Headers\' => \'Origin, X-Requested-With, Content-Type, Accept, Authorization, refresh_token\',
+ \'Access-Control-Allow-Methods\' => \'GET, POST, PUT, DELETE, OPTIONS\',
+ \'Access-Control-Allow-Credentials\' => true,
+];
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Api/Config/routes.php b/src/Libraries/Module/Templates/Default/Api/Config/routes.php
new file mode 100644
index 00000000..28b2af32
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Api/Config/routes.php
@@ -0,0 +1,20 @@
+group("openapi", function ($route) {
+ $route->get("docs", function (Quantum\Http\Response $response) {
+ $response->html(partial("openApi/openApi"));
+ });
+
+ $route->get("spec", function (Quantum\Http\Response $response) {
+ $fs = Quantum\Di\Di::get(Quantum\Libraries\Storage\FileSystem::class);
+ $response->json((array) json_decode($fs->get(modules_dir() . "\Api\Resources\openapi\spec.json")));
+ });
+ });
+ $route->get(\'/\', \'MainController\', \'index\');
+};';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/ApiController.php b/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/ApiController.php
new file mode 100644
index 00000000..bd926c60
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/ApiController.php
@@ -0,0 +1,56 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers\Abstracts;
+
+use Quantum\Mvc\QtController;
+
+/**
+ * Class ApiController
+ * @package Modules\Api
+ * @OA\Info(
+ * title="Quantum API documentation",
+ * version="2.9.0",
+ * description=" *Quantum Documentation: https://quantum.softberg.org/en/docs/v1/overview"
+ * ),
+ * @OA\SecurityScheme(
+ * securityScheme="bearer_token",
+ * type="apiKey",
+ * name="Authorization",
+ * in="header"
+ * )
+ */
+abstract class ApiController extends QtController
+{
+
+ /**
+ * Status error
+ */
+ const STATUS_ERROR = \'error\';
+
+ /**
+ * Status success
+ */
+ const STATUS_SUCCESS = \'success\';
+
+ /**
+ * CSRF verification
+ * @var bool
+ */
+ public $csrfVerification = false;
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/OpenApiMainController.php b/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/OpenApiMainController.php
new file mode 100644
index 00000000..5e111291
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Api/Controllers/Abstracts/OpenApiMainController.php
@@ -0,0 +1,64 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\\' . $this->moduleName . '\Controllers\Abstracts;
+
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class OpenApiPostController
+ * @package Modules\Api
+ */
+abstract class OpenApiMainController extends ApiController
+{
+
+ /**
+ * @OA\Info(
+ * title="' . $this->moduleName . '",
+ * version="1.0.0",
+ * description="This is the ' . $this->moduleName . ' module."
+ * )
+ */
+
+ /**
+ * @OA\Tag(
+ * name="' . $this->moduleName . '",
+ * description="Operations about the ' . $this->moduleName . '"
+ * )
+ */
+
+ /**
+ * @OA\Get(
+ * path="/' . strtolower($this->moduleName) . '",
+ * tags={"' . $this->moduleName . '"},
+ * summary="Get status of ' . $this->moduleName . '",
+ * description="Returns status of ' . $this->moduleName . ' module.",
+ * @OA\Response(
+ * response=200,
+ * description="Successful response",
+ * @OA\JsonContent(
+ * type="object",
+ * @OA\Property(property="status", type="string", example="success"),
+ * @OA\Property(property="message", type="string", example="' . $this->moduleName . ' module.")
+ * )
+ * )
+ * )
+ */
+ abstract public function index(Response $response);
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Api/Controllers/MainController.php b/src/Libraries/Module/Templates/Default/Api/Controllers/MainController.php
new file mode 100644
index 00000000..bf06a543
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Api/Controllers/MainController.php
@@ -0,0 +1,22 @@
+moduleName . '\Controllers;
+
+use Quantum\Factory\ViewFactory;
+use Quantum\Mvc\QtController;
+use Quantum\Http\Response;
+
+class MainController extends QtController
+{
+ private $name = "' . $this->moduleName . '";
+
+ public function index(Response $response)
+ {
+ $response->json([
+ \'status\' => \'success\',
+ \'message\' => $this->name . \' module.\'
+ ]);
+ }
+};';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Web/Config/routes.php b/src/Libraries/Module/Templates/Default/Web/Config/routes.php
new file mode 100644
index 00000000..59cdc7b1
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Web/Config/routes.php
@@ -0,0 +1,10 @@
+get(\'/\', \'MainController\', \'index\');
+};';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Web/Controllers/MainController.php b/src/Libraries/Module/Templates/Default/Web/Controllers/MainController.php
new file mode 100644
index 00000000..8e14bab1
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Web/Controllers/MainController.php
@@ -0,0 +1,21 @@
+moduleName . '\Controllers;
+
+use Quantum\Factory\ViewFactory;
+use Quantum\Mvc\QtController;
+use Quantum\Http\Response;
+
+class MainController extends QtController
+{
+ public function index(Response $response, ViewFactory $view)
+ {
+ $view->setLayout(\'layouts' . DS . 'main\');
+ $view->setParams([
+ \'title\' => config()->get(\'app_name\'),
+ ]);
+ $response->html($view->render(\'index\'));
+ }
+};';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Web/Views/index.php b/src/Libraries/Module/Templates/Default/Web/Views/index.php
new file mode 100644
index 00000000..f0188676
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Web/Views/index.php
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
' . strtoupper($this->moduleName) . ' HOME PAGE
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Web/Views/layouts/main.php b/src/Libraries/Module/Templates/Default/Web/Views/layouts/main.php
new file mode 100644
index 00000000..068891da
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Web/Views/layouts/main.php
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+ url(\'css/materialize.min.css\') ?>\' type=\'text/css\' media=\'screen,projection\' />
+ url(\'css/custom.css\') ?>\' type=\'text/css\' />
+
+
+
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Default/Web/Views/partials/bubbles.php b/src/Libraries/Module/Templates/Default/Web/Views/partials/bubbles.php
new file mode 100644
index 00000000..efd7c49e
--- /dev/null
+++ b/src/Libraries/Module/Templates/Default/Web/Views/partials/bubbles.php
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Config/auth.php b/src/Libraries/Module/Templates/Demo/Api/Config/auth.php
new file mode 100644
index 00000000..2249161d
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Config/auth.php
@@ -0,0 +1,25 @@
+ \'api\',
+ \'service\' => Shared\Services\AuthService::class,
+ \'claims\' => [
+ \'jti\' => uniqid(),
+ \'iss\' => \'issuer\',
+ \'aud\' => \'audience\',
+ \'iat\' => time(),
+ \'nbf\' => time() + 1,
+ \'exp\' => time() + 3600 // 1 hour
+ ]
+];
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Config/cors.php b/src/Libraries/Module/Templates/Demo/Api/Config/cors.php
new file mode 100644
index 00000000..fb67dbc5
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Config/cors.php
@@ -0,0 +1,17 @@
+ \'*\',
+ \'Access-Control-Allow-Headers\' => \'Origin, X-Requested-With, Content-Type, Accept, Authorization, refresh_token\',
+ \'Access-Control-Allow-Methods\' => \'GET, POST, PUT, DELETE, OPTIONS\',
+ \'Access-Control-Allow-Credentials\' => true,
+];
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Config/database.php b/src/Libraries/Module/Templates/Demo/Api/Config/database.php
new file mode 100644
index 00000000..b812e28a
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Config/database.php
@@ -0,0 +1,55 @@
+ \'sleekdb\',
+
+ /**
+ * ---------------------------------------------------------
+ * Database Connections
+ * ---------------------------------------------------------
+ *
+ * You can define as many database configurations as you want.
+ *
+ * driver : mysql, pgsql, sqlite
+ * host : The database server (localhost)
+ * dbname : The database name
+ * username : Username of the database server
+ * password : Password of the database server
+ * charset : Default charset
+ */
+ \'mysql\' => [
+ \'driver\' => env("DB_DRIVER", "mysql"),
+ \'host\' => env("DB_HOST", "localhost"),
+ \'dbname\' => env("DB_NAME"),
+ \'username\' => env("DB_USERNAME", "root"),
+ \'password\' => env("DB_PASSWORD"),
+ \'charset\' => env("DB_CHARSET", \'utf8\'),
+ \'orm\' => \Quantum\Libraries\Database\Idiorm\IdiormDbal::class
+ ],
+ \'sleekdb\' => [
+ \'driver\' => \'sleekdb\',
+ \'config\' => [
+ \'auto_cache\' => false,
+ \'cache_lifetime\' => null,
+ \'timeout\' => false,
+ \'search\' => [
+ \'min_length\' => 2,
+ \'mode\' => \'or\',
+ \'score_key\' => \'scoreKey\',
+ \'algorithm\' => 1
+ ],
+ ],
+ \'database_dir\' => base_dir() . DS . \'shared\' . DS . \'store\',
+ \'orm\' => \Quantum\Libraries\Database\Sleekdb\SleekDbal::class
+ ],
+];
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Config/routes.php b/src/Libraries/Module/Templates/Demo/Api/Config/routes.php
new file mode 100644
index 00000000..59c91515
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Config/routes.php
@@ -0,0 +1,38 @@
+group("openapi", function ($route) {
+ $route->get("docs", function (Quantum\Http\Response $response) {
+ $response->html(partial("openApi/openApi"));
+ });
+
+ $route->get("spec", function (Quantum\Http\Response $response) {
+ $fs = Quantum\Di\Di::get(Quantum\Libraries\Storage\FileSystem::class);
+ $response->json((array) json_decode($fs->get(modules_dir() . "\Api\Resources\openapi\spec.json")));
+ });
+ });
+
+ $route->get(\'[:alpha:2]?/posts\', \'PostController\', \'getPosts\');
+ $route->get(\'[:alpha:2]?/post/[id=:any]\', \'PostController\', \'getPost\')->middlewares([\'Post\']);
+
+ $route->post(\'[:alpha:2]?/signin\', \'AuthController\', \'signin\');
+ $route->post(\'[:alpha:2]?/signup\', \'AuthController\', \'signup\')->middlewares([\'Signup\']);
+ $route->post(\'[:alpha:2]?/forget\', \'AuthController\', \'forget\')->middlewares([\'Forget\']);
+ $route->get(\'[:alpha:2]?/activate/[token=:any]\', \'AuthController\', \'activate\')->middlewares([\'Activate\']);
+ $route->post(\'[:alpha:2]?/reset/[token=:any]\', \'AuthController\', \'reset\')->middlewares([\'Reset\']);
+ $route->get(\'[:alpha:2]?/resend/[code=:any]\', \'AuthController\', \'resend\')->middlewares([\'Resend\']);
+ $route->post(\'[:alpha:2]?/verify\', \'AuthController\', \'verify\')->middlewares([\'Verify\']);
+
+ $route->group(\'auth\', function ($route) {
+ $route->get(\'[:alpha:2]?/me\', \'AuthController\', \'me\');
+ $route->get(\'[:alpha:2]?/signout\', \'AuthController\', \'signout\')->middlewares([\'Signout\']);
+ $route->get(\'[:alpha:2]?/my-posts\', \'PostController\', \'myPosts\')->middlewares([\'Editor\']);
+ $route->post(\'[:alpha:2]?/my-posts/create\', \'PostController\', \'create\')->middlewares([\'Editor\']);
+ $route->add(\'[:alpha:2]?/my-posts/amend/[id=:any]\', \'PUT\', \'PostController\', \'amend\')->middlewares([\'Editor\', \'Owner\']);
+ $route->add(\'[:alpha:2]?/my-posts/delete/[id=:any]\', \'DELETE\', \'PostController\', \'delete\')->middlewares([\'Editor\', \'Owner\']);
+ $route->add(\'[:alpha:2]?/my-posts/delete-image/[id=:any]\', \'DELETE\', \'PostController\', \'deleteImage\')->middlewares([\'Editor\', \'Owner\']);
+ })->middlewares([\'Auth\']);
+};
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/ApiController.php b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/ApiController.php
new file mode 100644
index 00000000..bd926c60
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/ApiController.php
@@ -0,0 +1,56 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers\Abstracts;
+
+use Quantum\Mvc\QtController;
+
+/**
+ * Class ApiController
+ * @package Modules\Api
+ * @OA\Info(
+ * title="Quantum API documentation",
+ * version="2.9.0",
+ * description=" *Quantum Documentation: https://quantum.softberg.org/en/docs/v1/overview"
+ * ),
+ * @OA\SecurityScheme(
+ * securityScheme="bearer_token",
+ * type="apiKey",
+ * name="Authorization",
+ * in="header"
+ * )
+ */
+abstract class ApiController extends QtController
+{
+
+ /**
+ * Status error
+ */
+ const STATUS_ERROR = \'error\';
+
+ /**
+ * Status success
+ */
+ const STATUS_SUCCESS = \'success\';
+
+ /**
+ * CSRF verification
+ * @var bool
+ */
+ public $csrfVerification = false;
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiAuthController.php b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiAuthController.php
new file mode 100644
index 00000000..98397fa2
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiAuthController.php
@@ -0,0 +1,401 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers\Abstracts;
+
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class OpenApiAuthController
+ * @package Modules\Api
+ */
+abstract class OpenApiAuthController extends ApiController
+{
+
+ /**
+ * Sign in action
+ * @OA\Post(
+ * path="/api/signin",
+ * tags={"Authentication"},
+ * summary="Sign in action",
+ * operationId="userSignIn",
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="email",
+ * type="string"
+ * ),
+ * @OA\Property(
+ * property="password",
+ * type="string"
+ * ),
+ * example={"email": "rgaylord@gmail.com", "password": "password"}
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function signin(Request $request, Response $response);
+
+ /**
+ * Gets the logged-in user data
+ * @OA\Get(
+ * path="/api/me",
+ * tags={"User"},
+ * summary="Gets the logged-in user data",
+ * operationId="me",
+ * security={
+ * {"bearer_token": {}}
+ * },
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ */
+ abstract public function me(Response $response);
+
+ /**
+ * Sign out action
+ * @OA\Get(
+ * path="/api/signout",
+ * tags={"Authentication"},
+ * summary="Sign out action",
+ * operationId="signout",
+ * @OA\Parameter(
+ * name="refresh_token",
+ * description="Refresh token",
+ * required=true,
+ * in="header",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ */
+ abstract public function signout(Response $response);
+
+ /**
+ * Sign up action
+ * @OA\Post(
+ * path="/api/signup",
+ * tags={"Authentication"},
+ * summary="Sign up action",
+ * operationId="signUpApi",
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="email",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="password",
+ * type="string"
+ * ),
+ * @OA\Property(
+ * property="firstname",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="lastname",
+ * type="string",
+ * ),
+ * example={"email": "mail@example.com", "password": "password", "firstname": "Jon", "lastname": "Smit"}
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function signup(Request $request, Response $response);
+
+ /**
+ * Activate action
+ * @OA\Get(
+ * path="/api/activate/{activate_token}",
+ * tags={"Authentication"},
+ * summary="Activate action",
+ * operationId="activateProfile",
+ * @OA\Parameter(
+ * name="activate_token",
+ * description="Activate token",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function activate(Request $request, Response $response);
+
+ /**
+ * Forget action
+ * @OA\Post(
+ * path="/api/forget",
+ * tags={"Authentication"},
+ * summary="Forget action",
+ * operationId="forgetPassword",
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="username",
+ * type="string"
+ * ),
+ * example={"email": "mail@example.com"}
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function forget(Request $request, Response $response);
+
+ /**
+ * Reset action
+ * @OA\Post(
+ * path="/api/reset/{reset_token}",
+ * tags={"Authentication"},
+ * summary="Reset action",
+ * operationId="resetPassword",
+ * @OA\Parameter(
+ * name="reset_token",
+ * description="Reset token",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="password",
+ * type="string"
+ * ),
+ * @OA\Property(
+ * property="repeat_password",
+ * type="string"
+ * ),
+ * example={"password": "password", "repeat_password": "password"}
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function reset(Request $request, Response $response);
+
+ /**
+ * Verify action
+ * @OA\Post(
+ * path="/api/verify",
+ * tags={"Authentication"},
+ * summary="Verify action",
+ * operationId="accountVerify",
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * @OA\Schema(
+ * @OA\Property(
+ * property="otp_code",
+ * type="string"
+ * ),
+ * example={"otp": "123456", "code": "otp_token"}
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function verify(Request $request, Response $response);
+
+ /**
+ * Resend action
+ * @OA\Get(
+ * path="/api/resend/{otp_token}",
+ * tags={"Authentication"},
+ * summary="Resend action",
+ * operationId="resendOTP",
+ * @OA\Parameter(
+ * name="otp_token",
+ * description="OTP token",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ */
+ abstract public function resend(Response $response);
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiPostController.php b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiPostController.php
new file mode 100644
index 00000000..84ac1081
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Controllers/Abstracts/OpenApiPostController.php
@@ -0,0 +1,334 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers\Abstracts;
+
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class OpenApiPostController
+ * @package Modules\Api
+ */
+abstract class OpenApiPostController extends ApiController
+{
+
+ /**
+ * Get posts action
+ * @OA\Get(
+ * path="/api/posts",
+ * tags={"Posts"},
+ * summary="Get posts action",
+ * operationId="posts",
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ */
+ abstract public function posts(Response $response);
+
+ /**
+ * Get post action
+ * @OA\Get(
+ * path="/api/post/{id}",
+ * tags={"Posts"},
+ * summary="Get post action",
+ * operationId="post",
+ * @OA\Parameter(
+ * name="id",
+ * description="Post Id",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=404,
+ * description="Not Found"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param string|null $lang
+ * @param string $postId
+ * @param Response $response
+ */
+ abstract public function post(?string $lang, string $postId, Response $response);
+
+ /**
+ * Get my posts action
+ * @OA\Get(
+ * path="/api/my-posts",
+ * tags={"Posts"},
+ * summary="Get my posts action",
+ * operationId="myPosts",
+ * security={
+ * {"bearer_token": {}}
+ * },
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ */
+ abstract public function myPosts(Response $response);
+
+ /**
+ * Create post action
+ * @OA\Post(
+ * path="/api/my-posts/create",
+ * tags={"Posts"},
+ * summary="Create post action",
+ * operationId="create",
+ * security={
+ * {"bearer_token": {}
+ * }},
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="multipart/form-data",
+ * @OA\Schema(
+ * type="object",
+ * required={"title", "content"},
+ * @OA\Property(
+ * property="title",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="content",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="image",
+ * type="file",
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ */
+ abstract public function create(Request $request, Response $response);
+
+ /**
+ * Amend post action
+ * @OA\Put(
+ * path="/api/my-posts/amend/{id}",
+ * tags={"Posts"},
+ * summary="Amend post action",
+ * operationId="amend",
+ * security={
+ * {"bearer_token": {}
+ * }},
+ * @OA\Parameter(
+ * name="id",
+ * description="Post id",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\RequestBody(
+ * @OA\MediaType(
+ * mediaType="multipart/form-data",
+ * @OA\Schema(
+ * type="object",
+ * required={"title", "content"},
+ * @OA\Property(
+ * property="title",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="content",
+ * type="string",
+ * ),
+ * @OA\Property(
+ * property="image",
+ * type="file",
+ * )
+ * )
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Request $request
+ * @param Response $response
+ * @param string|null $lang
+ * @param string $postId
+ */
+ abstract public function amend(Request $request, Response $response, ?string $lang, string $postId);
+
+ /**
+ * Delete post action
+ * @OA\Delete(
+ * path="/api/my-posts/delete/{id}",
+ * tags={"Posts"},
+ * summary="Delete post action",
+ * operationId="delete",
+ * security={
+ * {"bearer_token": {}}
+ * },
+ * @OA\Parameter(
+ * name="id",
+ * description="Post id",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ * @param string|null $lang
+ * @param string $postId
+ */
+ abstract public function delete(Response $response, ?string $lang, string $postId);
+
+ /**
+ * Delete post image action
+ * @OA\Delete(
+ * path="/api/my-posts/delete-image/{id}",
+ * tags={"Posts"},
+ * summary="Delete post image action",
+ * operationId="deleteImage",
+ * security={
+ * {"bearer_token": {}
+ * }},
+ * @OA\Parameter(
+ * name="id",
+ * description="Post id",
+ * required=true,
+ * in="path",
+ * @OA\Schema(
+ * type="string"
+ * )
+ * ),
+ * @OA\Response(
+ * response=200,
+ * description="Success",
+ * @OA\MediaType(
+ * mediaType="application/json",
+ * )
+ * ),
+ * @OA\Response(
+ * response=401,
+ * description="Unauthorized Request"
+ * ),
+ * @OA\Response(
+ * response=422,
+ * description="Unprocessable Entity"
+ * ),
+ * @OA\Response(
+ * response=500,
+ * description="Internal Server Error"
+ * )
+ * )
+ * @param Response $response
+ * @param string|null $lang
+ * @param string $postId
+ */
+ abstract public function deleteImage(Response $response, ?string $lang, string $postId);
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Controllers/AuthController.php b/src/Libraries/Module/Templates/Demo/Api/Controllers/AuthController.php
new file mode 100644
index 00000000..4563c346
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Controllers/AuthController.php
@@ -0,0 +1,174 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers;
+
+use Modules\Api\Controllers\Abstracts\OpenApiAuthController;
+use Quantum\Exceptions\AuthException;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class AuthController
+ * @package Modules\Api
+ */
+class AuthController extends OpenApiAuthController
+{
+
+ /**
+ * @inheritDoc
+ */
+ public function signin(Request $request, Response $response)
+ {
+ try {
+ $code = auth()->signin($request->get(\'email\'), $request->get(\'password\'));
+
+ if (filter_var(config()->get(\'2FA\'), FILTER_VALIDATE_BOOLEAN)) {
+ $response->set(\'code\', $code);
+ }
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS
+ ]);
+ } catch (AuthException $e) {
+ $response->json([
+ \'status\' => self::STATUS_ERROR,
+ \'message\' => $e->getMessage()
+ ], 422);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function me(Response $response)
+ {
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS,
+ \'data\' => [
+ \'firstname\' => auth()->user()->firstname,
+ \'lastname\' => auth()->user()->lastname,
+ \'email\' => auth()->user()->email
+ ]
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function signout(Response $response)
+ {
+ if (auth()->signout()) {
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS
+ ]);
+ } else {
+ $response->json([
+ \'status\' => self::STATUS_ERROR,
+ \'message\' => t(\'validation.unauthorizedRequest\')
+ ]);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function signup(Request $request, Response $response)
+ {
+ auth()->signup($request->all());
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS,
+ \'message\' => t(\'common.successfully_signed_up\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function activate(Request $request, Response $response)
+ {
+ auth()->activate($request->get(\'activation_token\'));
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS,
+ \'message\' => t(\'common.account_activated\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function forget(Request $request, Response $response)
+ {
+ auth()->forget($request->get(\'email\'));
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS,
+ \'message\' => t(\'common.check_email\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function reset(Request $request, Response $response)
+ {
+ auth()->reset($request->get(\'reset_token\'), $request->get(\'password\'));
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function verify(Request $request, Response $response)
+ {
+ try {
+ auth()->verifyOtp((int)$request->get(\'otp\'), $request->get(\'code\'));
+
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS
+ ]);
+ } catch (AuthException $e) {
+ $response->json([
+ \'status\' => self::STATUS_ERROR,
+ \'message\' => $e->getMessage()
+ ]);
+ }
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function resend(Response $response)
+ {
+ try {
+ $response->json([
+ \'status\' => self::STATUS_SUCCESS,
+ \'code\' => auth()->resendOtp(route_param(\'code\'))
+ ]);
+ } catch (AuthException $e) {
+ $response->json([
+ \'status\' => self::STATUS_ERROR,
+ \'message\' => $e->getMessage()
+ ]);
+ }
+ }
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Controllers/PostController.php b/src/Libraries/Module/Templates/Demo/Api/Controllers/PostController.php
new file mode 100644
index 00000000..e82bb5de
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Controllers/PostController.php
@@ -0,0 +1,188 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Controllers;
+
+use Modules\Api\Controllers\Abstracts\OpenApiPostController;
+use Quantum\Factory\ServiceFactory;
+use Shared\Services\PostService;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class PostController
+ * @package Modules\Api
+ */
+class PostController extends OpenApiPostController
+{
+
+ /**
+ * Post service
+ * @var PostService
+ */
+ public $postService;
+
+ /**
+ * Works before an action
+ */
+ public function __before()
+ {
+ $this->postService = ServiceFactory::get(PostService::class);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function posts(Response $response)
+ {
+ $response->json([
+ \'status\' => \'success\',
+ \'data\' => $this->postService->getPosts()
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function post(?string $lang, string $postId, Response $response)
+ {
+ $response->json([
+ \'status\' => \'success\',
+ \'data\' => $this->postService->getPost($postId)
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function myPosts(Response $response)
+ {
+ $response->json([
+ \'status\' => \'success\',
+ \'data\' => $this->postService->getMyPosts((int)auth()->user()->id)
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function create(Request $request, Response $response)
+ {
+ $postData = [
+ \'user_id\' => (int)auth()->user()->id,
+ \'title\' => $request->get(\'title\', null, true),
+ \'content\' => $request->get(\'content\', null, true),
+ \'image\' => \'\',
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ];
+
+ if ($request->hasFile(\'image\')) {
+ $imageName = $this->postService->saveImage(
+ $request->getFile(\'image\'),
+ auth()->user()->uuid,
+ slugify($request->get(\'title\'))
+ );
+
+ $postData[\'image\'] = $imageName;
+ }
+
+ $this->postService->addPost($postData);
+
+ $response->json([
+ \'status\' => \'success\',
+ \'message\' => t(\'common.created_successfully\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function amend(Request $request, Response $response, ?string $lang, string $postId)
+ {
+ $postData = [
+ \'title\' => $request->get(\'title\', null, true),
+ \'content\' => $request->get(\'content\', null, true),
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ];
+
+ $post = $this->postService->getPost($postId, false);
+
+ if ($request->hasFile(\'image\')) {
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $imageName = $this->postService->saveImage(
+ $request->getFile(\'image\'),
+ auth()->user()->uuid,
+ slugify($request->get(\'title\'))
+ );
+
+ $postData[\'image\'] = $imageName;
+ }
+
+ $this->postService->updatePost($postId, $postData);
+
+ $response->json([
+ \'status\' => \'success\',
+ \'message\' => t(\'common.updated_successfully\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function delete(Response $response, ?string $lang, string $postId)
+ {
+ $post = $this->postService->getPost($postId, false);
+
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $this->postService->deletePost($postId);
+
+ $response->json([
+ \'status\' => \'success\',
+ \'message\' => t(\'common.deleted_successfully\')
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function deleteImage(Response $response, ?string $lang, string $postId)
+ {
+ $post = $this->postService->getPost($postId, false);
+
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $this->postService->updatePost($postId, [
+ \'title\' => $post[\'title\'],
+ \'content\' => $post[\'content\'],
+ \'image\' => \'\',
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ]);
+
+ $response->json([
+ \'status\' => \'success\',
+ \'message\' => t(\'common.deleted_successfully\')
+ ]);
+ }
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Activate.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Activate.php
new file mode 100644
index 00000000..1f2f206c
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Activate.php
@@ -0,0 +1,69 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Activate
+ * @package Modules\Api
+ */
+class Activate extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $token = route_param(\'token\');
+
+ if (!$token || !$this->checkToken($token)) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => [t(\'validation.nonExistingRecord\', \'token\')]
+ ], 422);
+
+ stop();
+ }
+
+ $request->set(\'activation_token\', $token);
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check token
+ * @param string $token
+ * @return bool
+ */
+ private function checkToken(string $token): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'activation_token\', $token)->asArray());
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Auth.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Auth.php
new file mode 100644
index 00000000..950fa350
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Auth.php
@@ -0,0 +1,52 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Auth
+ * @package Modules\Api
+ */
+class Auth extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!auth()->check()) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'validation.unauthorizedRequest\')
+ ], 401);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Editor.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Editor.php
new file mode 100644
index 00000000..d887f214
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Editor.php
@@ -0,0 +1,106 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Editor
+ * @package Modules\Api
+ */
+class Editor extends QtMiddleware
+{
+
+ /**
+ * Roles
+ */
+ const ROLES = [\'admin\', \'editor\'];
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ * @param Request $request
+ */
+ public function __construct(Request $request)
+ {
+ $this->validator = new Validator();
+
+ if ($request->hasFile(\'image\')) {
+ $this->validator->addRules([
+ \'image\' => [
+ Rule::set(\'fileSize\', 2 * pow(1024, 2)),
+ Rule::set(\'fileExtension\', [\'jpeg\', \'jpg\', \'png\']),
+ ]
+ ]);
+ }
+
+ $this->validator->addRules([
+ \'title\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 10),
+ Rule::set(\'maxLen\', 50),
+ ],
+ \'content\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 10),
+ Rule::set(\'maxLen\', 1000),
+ ]
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!in_array(auth()->user()->role, self::ROLES)) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'validation.unauthorizedRequest\')
+ ], 401);
+
+ stop();
+ }
+
+ if ($request->isMethod(\'post\') || $request->isMethod(\'put\')) {
+ if (!$this->validator->isValid($request->all())) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => $this->validator->getErrors()
+ ], 422);
+
+ stop();
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Forget.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Forget.php
new file mode 100644
index 00000000..033b118a
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Forget.php
@@ -0,0 +1,96 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Forget
+ * @package Modules\Api
+ */
+class Forget extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRule(\'email\', [
+ Rule::set(\'required\'),
+ Rule::set(\'email\')
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if ($request->isMethod(\'post\')) {
+ if (!$this->validator->isValid($request->all())) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => $this->validator->getErrors()
+ ], 422);
+
+ stop();
+ }
+
+ if (!$this->emailExists($request->get(\'email\'))) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => [t(\'validation.nonExistingRecord\', $request->get(\'email\'))]
+ ], 422);
+
+ stop();
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check for email existence
+ * @param string $email
+ * @return bool
+ */
+ private function emailExists(string $email): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'email\', $email)->asArray());
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Owner.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Owner.php
new file mode 100644
index 00000000..7a3aa912
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Owner.php
@@ -0,0 +1,58 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ServiceFactory;
+use Shared\Services\PostService;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Owner
+ * @package Modules\Api
+ */
+class Owner extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $postId = (string)route_param(\'id\');
+
+ $post = ServiceFactory::get(PostService::class)->getPost($postId, false);
+
+ if (!$post || $post[\'user_id\'] != auth()->user()->id) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'common.post_not_found\')
+ ], 404);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Post.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Post.php
new file mode 100644
index 00000000..85c87bf9
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Post.php
@@ -0,0 +1,58 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ServiceFactory;
+use Shared\Services\PostService;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Post
+ * @package Modules\Api
+ */
+class Post extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $postId = (string)route_param(\'id\');
+
+ $post = ServiceFactory::get(PostService::class)->getPost($postId, false);
+
+ if (!$post) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'common.post_not_found\')
+ ], 404);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Resend.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Resend.php
new file mode 100644
index 00000000..f5f0bbb3
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Resend.php
@@ -0,0 +1,52 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Resend
+ * @package Modules\Api
+ */
+class Resend extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!route_param(\'code\')) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'validation.required\', \'code\')
+ ], 422);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Reset.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Reset.php
new file mode 100644
index 00000000..58c6d1cd
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Reset.php
@@ -0,0 +1,124 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Reset
+ * @package Modules\Api
+ */
+class Reset extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRules([
+ \'password\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 6)
+ ],
+ \'repeat_password\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 6)
+ ]
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $token = route_param(\'token\');
+
+ if (!$token || !$this->checkToken($token)) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => [t(\'validation.nonExistingRecord\', \'token\')]
+ ], 422);
+
+ stop();
+ }
+
+ if (!$this->validator->isValid($request->all())) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => $this->validator->getErrors()
+ ], 422);
+
+ stop();
+ }
+
+ if (!$this->confirmPassword($request->get(\'password\'), $request->get(\'repeat_password\'))) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => t(\'validation.nonEqualValues\')
+ ], 422);
+
+ stop();
+ }
+
+ $request->set(\'reset_token\', $token);
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check token
+ * @param string $token
+ * @return bool
+ */
+ private function checkToken(string $token): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'reset_token\', $token)->asArray());
+ }
+
+ /**
+ * Checks the password and repeat password
+ * @param string $newPassword
+ * @param string $repeatPassword
+ * @return bool
+ */
+ private function confirmPassword(string $newPassword, string $repeatPassword): bool
+ {
+ return $newPassword == $repeatPassword;
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signout.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signout.php
new file mode 100644
index 00000000..7bca3de6
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signout.php
@@ -0,0 +1,51 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+namespace Modules\Api\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Signout
+ * @package Modules\Api
+ */
+class Signout extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!Request::hasHeader(\'refresh_token\')) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => [t(\'validation.nonExistingRecord\', \'token\')]
+ ], 422);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signup.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signup.php
new file mode 100644
index 00000000..0167b61f
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Signup.php
@@ -0,0 +1,92 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Signup
+ * @package Modules\Api
+ */
+class Signup extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addValidation(\'uniqueUser\', function ($value) {
+ $userModel = ModelFactory::get(User::class);
+ return empty($userModel->findOneBy(\'email\', $value)->asArray());
+ });
+
+ $this->validator->addRules([
+ \'email\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'email\'),
+ Rule::set(\'uniqueUser\')
+ ],
+ \'password\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 6)
+ ],
+ \'firstname\' => [
+ Rule::set(\'required\')
+ ],
+ \'lastname\' => [
+ Rule::set(\'required\')
+ ],
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!$this->validator->isValid($request->all())) {
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => $this->validator->getErrors()
+ ], 422);
+
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Api/Middlewares/Verify.php b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Verify.php
new file mode 100644
index 00000000..2468b967
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Api/Middlewares/Verify.php
@@ -0,0 +1,78 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Api\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Verify
+ * @package Modules\Api
+ */
+
+class Verify extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRules([
+ \'otp\' => [
+ Rule::set(\'required\')
+ ],
+ \'code\' => [
+ Rule::set(\'required\')
+ ],
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if ($request->isMethod(\'post\')) {
+ if (!$this->validator->isValid($request->all())) {
+
+ $response->json([
+ \'status\' => \'error\',
+ \'message\' => $this->validator->getErrors()
+ ], 422);
+
+ stop();
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Config/auth.php b/src/Libraries/Module/Templates/Demo/Web/Config/auth.php
new file mode 100644
index 00000000..70d8239c
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Config/auth.php
@@ -0,0 +1,18 @@
+ 'web',
+ 'service' => Shared\Services\AuthService::class
+];
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Config/database.php b/src/Libraries/Module/Templates/Demo/Web/Config/database.php
new file mode 100644
index 00000000..1bb318a5
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Config/database.php
@@ -0,0 +1,54 @@
+ 'sleekdb',
+
+ /**
+ * ---------------------------------------------------------
+ * Database Connections
+ * ---------------------------------------------------------
+ *
+ * You can define as many database configurations as you want.
+ *
+ * driver : mysql, pgsql, sqlite
+ * host : The database server (localhost)
+ * dbname : The database name
+ * username : Username of the database server
+ * password : Password of the database server
+ * charset : Default charset
+ */
+ 'mysql' => array(
+ 'driver' => env(\"DB_DRIVER\", \"mysql\"),
+ 'host' => env(\"DB_HOST\", \"localhost\"),
+ 'dbname' => env(\"DB_NAME\"),
+ 'username' => env(\"DB_USERNAME\", \"root\"),
+ 'password' => env(\"DB_PASSWORD\"),
+ 'charset' => env(\"DB_CHARSET\", 'utf8'),
+ 'orm' => \Quantum\Libraries\Database\Idiorm\IdiormDbal::class
+ ),
+ 'sleekdb' => [
+ 'driver' => 'sleekdb',
+ 'config' => [
+ 'auto_cache' => false,
+ 'cache_lifetime' => null,
+ 'timeout' => false,
+ 'search' => [
+ 'min_length' => 2,
+ 'mode' => 'or',
+ 'score_key' => 'scoreKey',
+ 'algorithm' => 1
+ ],
+ ],
+ 'database_dir' => base_dir() . DS . 'shared' . DS . 'store',
+ 'orm' => \Quantum\Libraries\Database\Sleekdb\SleekDbal::class
+ ],
+];";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Config/routes.php b/src/Libraries/Module/Templates/Demo/Web/Config/routes.php
new file mode 100644
index 00000000..aa66b8cc
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Config/routes.php
@@ -0,0 +1,62 @@
+get(\'[:alpha:2]?\', function (Response $response, ViewFactory $view) {
+ $view->setLayout(\'layouts/main\');
+
+ $view->setParams([
+ \'title\' => config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\')
+ ]);
+
+ $response->html($view->render(\'index\'));
+ })->name(\'home\');
+
+ $route->get(\'[:alpha:2]?/about\', function (Response $response, ViewFactory $view) {
+ $view->setLayout(\'layouts/main\');
+
+ $view->setParams([
+ \'title\' => t(\'common.about\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\')
+ ]);
+
+ $response->html($view->render(\'about\'));
+ })->name(\'about\');
+
+ $route->get(\'/auth\', \'DropboxController\', \'auth\');
+ $route->get(\'/confirm\', \'DropboxController\', \'confirm\');
+ $route->get(\'/test\', \'DropboxController\', \'test\');
+ $route->get(\'/image/[:any]\', \'DropboxController\', \'image\');
+ $route->get(\'/upload\', \'DropboxController\', \'upload\');
+ $route->get(\'/list\', \'DropboxController\', \'list\');
+
+ $route->get(\'[:alpha:2]?/posts\', \'PostController\', \'posts\');
+ $route->get(\'[:alpha:2]?/post/[id=:any]\', \'PostController\', \'post\')->middlewares([\'Post\']);
+
+ $route->group(\'guest\', function ($route) {
+ $route->add(\'[:alpha:2]?/signin\', \'GET|POST\', \'AuthController\', \'signin\')->name(\'signin\');
+ $route->add(\'[:alpha:2]?/signup\', \'GET|POST\', \'AuthController\', \'signup\')->middlewares([\'Signup\'])->name(\'signup\');
+ $route->get(\'[:alpha:2]?/activate/[token=:any]\', \'AuthController\', \'activate\')->middlewares([\'Activate\']);
+ $route->add(\'[:alpha:2]?/forget\', \'GET|POST\', \'AuthController\', \'forget\')->middlewares([\'Forget\']);
+ $route->add(\'[:alpha:2]?/reset/[token=:any]\', \'GET|POST\', \'AuthController\', \'reset\')->middlewares([\'Reset\']);
+ $route->get(\'[:alpha:2]?/resend/[code=:any]\', \'AuthController\', \'resend\')->middlewares([\'Resend\']);
+ $route->add(\'[:alpha:2]?/verify/[code=:any]?\', \'GET|POST\', \'AuthController\', \'verify\')->middlewares([\'Verify\']);
+ })->middlewares([\'Guest\']);
+
+ $route->group(\'auth\', function ($route) {
+ $route->get(\'[:alpha:2]?/signout\', \'AuthController\', \'signout\');
+ $route->get(\'[:alpha:2]?/my-posts\', \'PostController\', \'myPosts\')->middlewares([\'Editor\']);
+ $route->get(\'[:alpha:2]?/my-posts/create\', \'PostController\', \'createFrom\')->middlewares([\'Editor\']);
+ $route->post(\'[:alpha:2]?/my-posts/create\', \'PostController\', \'create\')->middlewares([\'Editor\']);
+ $route->get(\'[:alpha:2]?/my-posts/amend/[id=:any]\', \'PostController\', \'amendForm\')->middlewares([\'Editor\', \'Owner\']);
+ $route->post(\'[:alpha:2]?/my-posts/amend/[id=:any]\', \'PostController\', \'amend\')->middlewares([\'Editor\', \'Owner\']);
+ $route->get(\'[:alpha:2]?/my-posts/delete/[id=:any]\', \'PostController\', \'delete\')->middlewares([\'Editor\', \'Owner\']);
+ $route->get(\'[:alpha:2]?/my-posts/delete-image/[id=:any]\', \'PostController\', \'deleteImage\')->middlewares([\'Editor\', \'Owner\']);
+ })->middlewares([\'Auth\']);
+};
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Controllers/AuthController.php b/src/Libraries/Module/Templates/Demo/Web/Controllers/AuthController.php
new file mode 100644
index 00000000..e3ab4c1d
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Controllers/AuthController.php
@@ -0,0 +1,227 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.9.0
+ */
+
+namespace Modules\Web\Controllers;
+
+use Quantum\Exceptions\AuthException;
+use Quantum\Factory\ViewFactory;
+use Quantum\Mvc\QtController;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class AuthController
+ * @package Modules\Web\Controllers
+ */
+class AuthController extends QtController
+{
+
+ /**
+ * Auth layout
+ */
+ const LAYOUT = \'layouts/main\';
+
+ /**
+ * Signin view
+ */
+ const VIEW_SIGNIN = \'auth/signin\';
+
+ /**
+ * Signup view
+ */
+ const VIEW_SIGNUP = \'auth/signup\';
+
+ /**
+ * Forget view
+ */
+ const VIEW_FORGET = \'auth/forget\';
+
+ /**
+ * Reset view
+ */
+ const VIEW_RESET = \'auth/reset\';
+
+ /**
+ * Reset view
+ */
+ const VIEW_VERIFY = \'auth/verify\';
+
+ /**
+ * Magic __before
+ * @param ViewFactory $view
+ */
+ public function __before(ViewFactory $view)
+ {
+ $view->setLayout(self::LAYOUT);
+ }
+
+ /**
+ * Sign in action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function signin(Request $request, Response $response, ViewFactory $view)
+ {
+ if ($request->isMethod(\'post\')) {
+ try {
+ $code = auth()->signin($request->get(\'email\'), $request->get(\'password\'), !!$request->get(\'remember\'));
+
+ if (filter_var(config()->get(\'2FA\'), FILTER_VALIDATE_BOOLEAN)) {
+ redirect(base_url(true) . \'/\' . current_lang() . \'/verify/\' . $code);
+ } else {
+ redirect(base_url(true) . \'/\' . current_lang());
+ }
+ } catch (AuthException $e) {
+ session()->setFlash(\'error\', $e->getMessage());
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ }
+ } else {
+ $view->setParams([
+ \'title\' => t(\'common.signin\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\')
+ ]);
+
+ $response->html($view->render(self::VIEW_SIGNIN));
+ }
+ }
+
+ /**
+ * Sign out action
+ */
+ public function signout()
+ {
+ auth()->signout();
+ redirect(base_url(true) . \'/\' . current_lang());
+ }
+
+ /**
+ * Sign up action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function signup(Request $request, Response $response, ViewFactory $view)
+ {
+ if ($request->isMethod(\'post\')) {
+ auth()->signup($request->all());
+ session()->setFlash(\'success\', t(\'common.check_email_signup\'));
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signup\');
+ } else {
+ $view->setParams([
+ \'title\' => t(\'common.signup\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\')
+ ]);
+
+ $response->html($view->render(self::VIEW_SIGNUP));
+ }
+ }
+
+ /**
+ * Activate action
+ * @param Request $request
+ */
+ public function activate(Request $request)
+ {
+ auth()->activate($request->get(\'activation_token\'));
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ }
+
+ /**
+ * Forget action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function forget(Request $request, Response $response, ViewFactory $view)
+ {
+ if ($request->isMethod(\'post\')) {
+ auth()->forget($request->get(\'email\'));
+ session()->setFlash(\'success\', t(\'common.check_email\'));
+ redirect(base_url(true) . \'/\' . current_lang() . \'/forget\');
+ } else {
+ $view->setParams([
+ \'title\' => t(\'common.forget_password\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ ]);
+
+ $response->html($view->render(self::VIEW_FORGET));
+ }
+ }
+
+ /**
+ * Reset action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function reset(Request $request, Response $response, ViewFactory $view)
+ {
+ if ($request->isMethod(\'post\')) {
+ auth()->reset($request->get(\'reset_token\'), $request->get(\'password\'));
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ } else {
+ $view->setParams([
+ \'title\' => t(\'common.reset_password\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'reset_token\' => $request->get(\'reset_token\')
+ ]);
+
+ $response->html($view->render(self::VIEW_RESET));
+ }
+ }
+
+ /**
+ * Verify OTP action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function verify(Request $request, Response $response, ViewFactory $view)
+ {
+ if ($request->isMethod(\'post\')) {
+ try {
+ auth()->verifyOtp((int)$request->get(\'otp\'), $request->get(\'code\'));
+ redirect(base_url(true) . \'/\' . current_lang());
+ } catch (AuthException $e) {
+ session()->setFlash(\'error\', $e->getMessage());
+ redirect(base_url(true) . \'/\' . current_lang() . \'/verify/\' . $request->get(\'code\'));
+ }
+ } else {
+ $view->setParams([
+ \'title\' => t(\'common.2fa\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'code\' => route_param(\'code\')
+ ]);
+
+ $response->html($view->render(self::VIEW_VERIFY));
+ }
+ }
+
+ /**
+ * Resend OTP action
+ */
+ public function resend()
+ {
+ try {
+ $otpToken = auth()->resendOtp(route_param(\'code\'));
+ redirect(base_url(true) . \'/\' . current_lang() . \'/verify/\' . $otpToken);
+ } catch (AuthException $e) {
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ }
+ }
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Controllers/PostController.php b/src/Libraries/Module/Templates/Demo/Web/Controllers/PostController.php
new file mode 100644
index 00000000..85b823c5
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Controllers/PostController.php
@@ -0,0 +1,245 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Controllers;
+
+use Quantum\Factory\ServiceFactory;
+use Quantum\Factory\ViewFactory;
+use Shared\Services\AuthService;
+use Shared\Services\PostService;
+use Quantum\Mvc\QtController;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+
+/**
+ * Class PostController
+ * @package Modules\Web\Controllers
+ */
+class PostController extends QtController
+{
+
+ /**
+ * Post service
+ * @var PostService
+ */
+ public $postService;
+
+ /**
+ * Post service
+ * @var AuthService
+ */
+ public $userService;
+
+ /**
+ * Works before an action
+ * @param ViewFactory $view
+ */
+ public function __before(ViewFactory $view)
+ {
+ $this->postService = ServiceFactory::get(PostService::class);
+ $this->userService = ServiceFactory::get(AuthService::class);
+
+ $view->setLayout(\'layouts/main\');
+ }
+
+ /**
+ * Get posts action
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function posts(Response $response, ViewFactory $view)
+ {
+ $view->setParams([
+ \'title\' => t(\'common.posts\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'posts\' => $this->postService->getPosts()
+ ]);
+
+ $response->html($view->render(\'post/post\'));
+ }
+
+ /**
+ * Get post action
+ * @param string|null $lang
+ * @param string $postId
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function post(?string $lang, string $postId, Response $response, ViewFactory $view)
+ {
+ $post = $this->postService->getPost($postId);
+
+ $view->setParams([
+ \'title\' => $post[\'title\'] . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'post\' => $post
+ ]);
+
+ $response->html($view->render(\'post/single\'));
+ }
+
+ /**
+ * Get my posts action
+ * @param Request $request
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function myPosts(Request $request, Response $response, ViewFactory $view)
+ {
+ $view->setParams([
+ \'title\' => t(\'common.my_posts\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'posts\' => $this->postService->getMyPosts((int)auth()->user()->id)
+ ]);
+
+ $response->html($view->render(\'post/my-posts\'));
+ }
+
+ /**
+ * Create post form
+ * @param Response $response
+ * @param ViewFactory $view
+ */
+ public function createFrom(Response $response, ViewFactory $view)
+ {
+ $view->setParams([
+ \'title\' => t(\'common.new_post\') . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\')
+ ]);
+
+ $response->html($view->render(\'post/form\'));
+ }
+
+ /**
+ * Create post action
+ * @param Request $request
+ */
+ public function create(Request $request)
+ {
+ $postData = [
+ \'user_id\' => (int)auth()->user()->id,
+ \'title\' => $request->get(\'title\', null, true),
+ \'content\' => $request->get(\'content\', null, true),
+ \'image\' => \'\',
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ];
+
+ if ($request->hasFile(\'image\')) {
+ $imageName = $this->postService->saveImage(
+ $request->getFile(\'image\'),
+ auth()->user()->uuid,
+ slugify($request->get(\'title\'))
+ );
+
+ $postData[\'image\'] = $imageName;
+ }
+
+ $this->postService->addPost($postData);
+
+ redirect(base_url(true) . \'/\' . current_lang() . \'/my-posts\');
+ }
+
+ public function amendForm(Response $response, ViewFactory $view, ?string $lang, string $postId)
+ {
+ $post = $this->postService->getPost($postId);
+
+ $view->setParams([
+ \'title\' => $post[\'title\'] . \' | \' . config()->get(\'app_name\'),
+ \'langs\' => config()->get(\'langs\'),
+ \'post\' => $post
+ ]);
+
+ $response->html($view->render(\'post/form\'));
+ }
+
+ /**
+ * Amend post action
+ * @param Request $request
+ * @param string|null $lang
+ * @param string $postId
+ */
+ public function amend(Request $request, ?string $lang, string $postId)
+ {
+ $postData = [
+ \'title\' => $request->get(\'title\', null, true),
+ \'content\' => $request->get(\'content\', null, true),
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ];
+
+ $post = $this->postService->getPost($postId, false);
+
+ if ($request->hasFile(\'image\')) {
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $imageName = $this->postService->saveImage(
+ $request->getFile(\'image\'),
+ auth()->user()->uuid,
+ slugify($request->get(\'title\'))
+ );
+
+ $postData[\'image\'] = $imageName;
+ }
+
+ $this->postService->updatePost($postId, $postData);
+
+ redirect(base_url(true) . \'/\' . current_lang() . \'/my-posts\');
+ }
+
+ /**
+ * Delete post action
+ * @param string|null $lang
+ * @param string $postId
+ */
+ public function delete(?string $lang, string $postId)
+ {
+ $post = $this->postService->getPost($postId, false);
+
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $this->postService->deletePost($postId);
+
+ redirect(base_url(true) . \'/\' . current_lang() . \'/my-posts\');
+ }
+
+ /**
+ * Delete post image action
+ * @param string|null $lang
+ * @param string $postId
+ */
+ public function deleteImage(?string $lang, string $postId)
+ {
+ $post = $this->postService->getPost($postId, false);
+
+ if ($post[\'image\']) {
+ $this->postService->deleteImage(auth()->user()->uuid . DS . $post[\'image\']);
+ }
+
+ $this->postService->updatePost($postId, [
+ \'title\' => $post[\'title\'],
+ \'content\' => $post[\'content\'],
+ \'image\' => \'\',
+ \'updated_at\' => date(\'Y-m-d H:i:s\'),
+ ]);
+
+ redirect(base_url(true) . \'/\' . current_lang() . \'/my-posts\');
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Activate.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Activate.php
new file mode 100644
index 00000000..66b58ad4
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Activate.php
@@ -0,0 +1,65 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Activate
+ * @package Modules\Web\Middlewares
+ */
+class Activate extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $token = (string) route_param(\'token\');
+
+ if (!$this->checkToken($token)) {
+ stop(function () use ($response) {
+ $response->html(partial(\'errors/404\'), 404);
+ });
+ }
+
+ $request->set(\'activation_token\', $token);
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check token
+ * @param string $token
+ * @return bool
+ */
+ private function checkToken(string $token): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'activation_token\', $token)->asArray());
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Auth.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Auth.php
new file mode 100644
index 00000000..87639389
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Auth.php
@@ -0,0 +1,46 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Auth
+ * @package Modules\Web\Middlewares
+ */
+class Auth extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!auth()->check()) {
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Editor.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Editor.php
new file mode 100644
index 00000000..fe5d4800
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Editor.php
@@ -0,0 +1,98 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Editor
+ * @package Modules\Web\Middlewares
+ */
+class Editor extends QtMiddleware
+{
+
+ /**
+ * Roles
+ */
+ const ROLES = [\'admin\', \'editor\'];
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ * @param Request $request
+ */
+ public function __construct(Request $request)
+ {
+ $this->validator = new Validator();
+
+ if ($request->hasFile(\'image\')) {
+ $this->validator->addRules([
+ \'image\' => [
+ Rule::set(\'fileSize\', 2 * pow(1024, 2)),
+ Rule::set(\'fileExtension\', [\'jpeg\', \'jpg\', \'png\']),
+ ]
+ ]);
+ }
+
+ $this->validator->addRules([
+ \'title\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 10),
+ Rule::set(\'maxLen\', 50)
+ ],
+ \'content\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 10),
+ Rule::set(\'maxLen\', 1000),
+ ],
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!in_array(auth()->user()->role, self::ROLES)) {
+ redirect(base_url(true) . \'/\' . current_lang());
+ }
+
+ if ($request->isMethod(\'post\')) {
+ if (!$this->validator->isValid($request->all())) {
+ $data = $request->all();
+ unset($data[\'image\']);
+ session()->setFlash(\'error\', $this->validator->getErrors());
+ redirectWith(get_referrer(), $data);
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Forget.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Forget.php
new file mode 100644
index 00000000..0b9ac8f6
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Forget.php
@@ -0,0 +1,94 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Exception;
+use Closure;
+
+/**
+ * Class Forget
+ * @package Modules\Web\Middlewares
+ */
+class Forget extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRule(\'email\', [
+ Rule::set(\'required\'),
+ Rule::set(\'email\')
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if ($request->isMethod(\'post\')) {
+ if (!$this->validator->isValid($request->all())) {
+ session()->setFlash(\'error\', $this->validator->getErrors());
+ redirect(base_url(true) . \'/\' . current_lang() . \'/forget\');
+ }
+
+ if (!$this->emailExists($request->get(\'email\'))) {
+ session()->setFlash(\'error\', [
+ \'email\' => [
+ t(\'validation.nonExistingRecord\', $request->get(\'email\'))
+ ]
+ ]);
+
+ redirect(base_url(true) . \'/\' . current_lang() . \'/forget\');
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check for email existence
+ * @param string $email
+ * @return bool
+ * @throws Exception
+ */
+ private function emailExists(string $email): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'email\', $email)->asArray());
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Guest.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Guest.php
new file mode 100644
index 00000000..b05c26d2
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Guest.php
@@ -0,0 +1,46 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Guest
+ * @package Modules\Web\Middlewares
+ */
+class Guest extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (auth()->check()) {
+ redirect(get_referrer() ?? base_url(true) . \'/\' . current_lang());
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Owner.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Owner.php
new file mode 100644
index 00000000..1b5b85bd
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Owner.php
@@ -0,0 +1,53 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ServiceFactory;
+use Shared\Services\PostService;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Editor
+ * @package Modules\Web\Middlewares
+ */
+class Owner extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $postId = (string) route_param(\'id\');
+
+ $post = ServiceFactory::get(PostService::class)->getPost($postId, false);
+
+ if (!$post || $post[\'user_id\'] != auth()->user()->id) {
+ $response->html(partial(\'errors/404\'), 404);
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Post.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Post.php
new file mode 100644
index 00000000..c037c0d9
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Post.php
@@ -0,0 +1,53 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ServiceFactory;
+use Shared\Services\PostService;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Editor
+ * @package Modules\Web\Middlewares
+ */
+class Post extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $postId = (string) route_param(\'id\');
+
+ $post = ServiceFactory::get(PostService::class)->getPost($postId, false);
+
+ if (!$post) {
+ $response->html(partial(\'errors/404\'), 404);
+ stop();
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Resend.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Resend.php
new file mode 100644
index 00000000..a7713144
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Resend.php
@@ -0,0 +1,46 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Closure;
+
+/**
+ * Class Resend
+ * @package Modules\Web\Middlewares
+ */
+class Resend extends QtMiddleware
+{
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if (!route_param(\'code\')) {
+ redirect(base_url(true) . \'/\' . current_lang() . \'/signin\');
+ }
+
+ return $next($request, $response);
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Reset.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Reset.php
new file mode 100644
index 00000000..1399b236
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Reset.php
@@ -0,0 +1,117 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Reset
+ * @package Modules\Web\Middlewares
+ */
+class Reset extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRule(\'password\', [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 6)
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ $token = route_param(\'token\');
+
+ if ($token && $request->isMethod(\'post\')) {
+ if (!$this->checkToken($token)) {
+ session()->setFlash(\'error\', [
+ \'password\' => [
+ t(\'validation.nonExistingRecord\', \'token\')
+ ]
+ ]);
+
+ redirect(get_referrer());
+ }
+
+ if (!$this->validator->isValid($request->all())) {
+ session()->setFlash(\'error\', $this->validator->getErrors());
+ redirect(get_referrer());
+ }
+
+ if (!$this->confirmPassword($request->get(\'password\'), $request->get(\'repeat_password\'))) {
+ session()->setFlash(\'error\', t(\'validation.nonEqualValues\'));
+ redirect(get_referrer());
+ }
+ } elseif ($request->isMethod(\'get\')) {
+ if (!$this->checkToken($token)) {
+ $response->html(partial(\'errors/404\'), 404);
+ stop();
+ }
+ }
+
+ $request->set(\'reset_token\', $token);
+
+ return $next($request, $response);
+ }
+
+ /**
+ * Check token
+ * @param string $token
+ * @return bool
+ */
+ private function checkToken(string $token): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'reset_token\', $token)->asArray());
+ }
+
+ /**
+ * Checks the password and repeat password
+ * @param string $newPassword
+ * @param string $repeatPassword
+ * @return bool
+ */
+ private function confirmPassword(string $newPassword, string $repeatPassword): bool
+ {
+ return $newPassword == $repeatPassword;
+ }
+
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Signup.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Signup.php
new file mode 100644
index 00000000..19f90d73
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Signup.php
@@ -0,0 +1,101 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Signup
+ * @package Modules\Web\Middlewares
+ */
+class Signup extends QtMiddleware
+{
+
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ * @throws \Exception
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addValidation(\'uniqueUser\', function ($value) {
+ $userModel = ModelFactory::get(User::class);
+ return empty($userModel->findOneBy(\'email\', $value)->asArray());
+ });
+
+ $this->validator->addRules([
+ \'email\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'email\'),
+ Rule::set(\'uniqueUser\')
+ ],
+ \'password\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'minLen\', 6)
+ ],
+ \'firstname\' => [
+ Rule::set(\'required\')
+ ],
+ \'lastname\' => [
+ Rule::set(\'required\')
+ ],
+ \'recaptcha\' => [
+ Rule::set(\'required\'),
+ Rule::set(\'recaptcha\')
+ ]
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if ($request->isMethod(\'post\')) {
+
+ if($request->has(\'g-recaptcha-response\')) {
+ $request->set(\'recaptcha\', $request->get(\'g-recaptcha-response\'));
+ $request->delete(\'g-recaptcha-response\');
+ }
+
+ if (!$this->validator->isValid($request->all())) {
+ session()->setFlash(\'error\', $this->validator->getErrors());
+ redirectWith(base_url(true) . \'/\' . current_lang() . \'/signup\', $request->all());
+ }
+ }
+
+ return $next($request, $response);
+ }
+
+}
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Middlewares/Verify.php b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Verify.php
new file mode 100644
index 00000000..07d1ff97
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Middlewares/Verify.php
@@ -0,0 +1,87 @@
+
+ * @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
+ * @link http://quantum.softberg.org/
+ * @since 2.8.0
+ */
+
+namespace Modules\Web\Middlewares;
+
+use Quantum\Libraries\Validation\Validator;
+use Quantum\Libraries\Validation\Rule;
+use Quantum\Middleware\QtMiddleware;
+use Quantum\Factory\ModelFactory;
+use Quantum\Http\Response;
+use Quantum\Http\Request;
+use Shared\Models\User;
+use Closure;
+
+/**
+ * Class Verify
+ * @package Modules\Web\Middlewares
+ */
+class Verify extends QtMiddleware
+{
+ /**
+ * @var Validator
+ */
+ private $validator;
+
+ /**
+ * Class constructor
+ */
+ public function __construct()
+ {
+ $this->validator = new Validator();
+
+ $this->validator->addRules([
+ \'otp\' => [
+ Rule::set(\'required\')
+ ],
+ \'code\' => [
+ Rule::set(\'required\')
+ ]
+ ]);
+ }
+
+ /**
+ * @param Request $request
+ * @param Response $response
+ * @param Closure $next
+ * @return mixed
+ */
+ public function apply(Request $request, Response $response, Closure $next)
+ {
+ if ($request->isMethod(\'post\')) {
+ if (!$this->validator->isValid($request->all())) {
+ session()->setFlash(\'error\', $this->validator->getErrors());
+ redirectWith(base_url(true) . \'/\' . current_lang() . \'/verify\', $request->all());
+ }
+ } else {
+ $token = (string)route_param(\'code\');
+
+ if (!$this->checkToken($token)) {
+ stop(function () use ($response) {
+ $response->html(partial(\'errors/404\'), 404);
+ });
+ }
+
+ }
+
+ return $next($request, $response);
+ }
+
+ private function checkToken(string $token): bool
+ {
+ $userModel = ModelFactory::get(User::class);
+ return !empty($userModel->findOneBy(\'otp_token\', $token)->asArray());
+ }
+}';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/about.php b/src/Libraries/Module/Templates/Demo/Web/Views/about.php
new file mode 100644
index 00000000..5a1f4cc0
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/about.php
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ > composer create-project quantum/project [project name]
+
+
+
+ > php qt serve
+
+
+
+
+
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/auth/forget.php b/src/Libraries/Module/Templates/Demo/Web/Views/auth/forget.php
new file mode 100644
index 00000000..1124a1c6
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/auth/forget.php
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ has(\'error\')): ?>
+
+
+
+ has(\'success\')): ?>
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/auth/reset.php b/src/Libraries/Module/Templates/Demo/Web/Views/auth/reset.php
new file mode 100644
index 00000000..6924d540
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/auth/reset.php
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+ has(\'error\')): ?>
+
+
+
+ has(\'success\')): ?>
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/auth/signin.php b/src/Libraries/Module/Templates/Demo/Web/Views/auth/signin.php
new file mode 100644
index 00000000..e271375d
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/auth/signin.php
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+ has(\'error\')): ?>
+
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/auth/signup.php b/src/Libraries/Module/Templates/Demo/Web/Views/auth/signup.php
new file mode 100644
index 00000000..c42524bc
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/auth/signup.php
@@ -0,0 +1,64 @@
+
+
+
+
+
+
+ has(\'error\')) : ?>
+
+
+
+ has(\'success\')): ?>
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/auth/verify.php b/src/Libraries/Module/Templates/Demo/Web/Views/auth/verify.php
new file mode 100644
index 00000000..b4eb5ded
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/auth/verify.php
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+ has(\'error\')): ?>
+
+
+
+ has(\'success\')): ?>
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/index.php b/src/Libraries/Module/Templates/Demo/Web/Views/index.php
new file mode 100644
index 00000000..ca744bdb
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/index.php
@@ -0,0 +1,22 @@
+
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/layouts/main.php b/src/Libraries/Module/Templates/Demo/Web/Views/layouts/main.php
new file mode 100644
index 00000000..35dfd57c
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/layouts/main.php
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/bubbles.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/bubbles.php
new file mode 100644
index 00000000..b5db5dc7
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/bubbles.php
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/footer.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/footer.php
new file mode 100644
index 00000000..eeba5671
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/footer.php
@@ -0,0 +1,16 @@
+
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/language.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/language.php
new file mode 100644
index 00000000..20de720e
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/language.php
@@ -0,0 +1,17 @@
+
+
+language
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/logo.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/logo.php
new file mode 100644
index 00000000..520959d8
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/logo.php
@@ -0,0 +1,7 @@
+\">
+ /assets/images/quantum-logo-white.png\" alt=\"\"/>
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/error.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/error.php
new file mode 100644
index 00000000..a02939ad
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/error.php
@@ -0,0 +1,20 @@
+
+ getFlash(\'error\') ?>
+
+
+
+ $messages): ?>
+
+
+
+
+
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/success.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/success.php
new file mode 100644
index 00000000..73db9ed6
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/messages/success.php
@@ -0,0 +1,8 @@
+
+ getFlash(\'success\'); ?>
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/navbar.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/navbar.php
new file mode 100644
index 00000000..bc81eeb3
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/navbar.php
@@ -0,0 +1,60 @@
+
+
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/partials/sidebar.php b/src/Libraries/Module/Templates/Demo/Web/Views/partials/sidebar.php
new file mode 100644
index 00000000..da33615a
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/partials/sidebar.php
@@ -0,0 +1,56 @@
+
+
+ \">
+home
+
+
+
+
+ /posts\">
+ assignment
+
+
+
+check()) : ?>
+
+
+ person
+ user()->firstname . ' ' . auth()->user()->lastname ?>
+ arrow_drop_down
+
+
+
+
+
+
+ /signup\">
+ person_add
+
+
+
+
+
+
+ /signin\">
+ exit_to_app
+
+
+
+
+
+ 'sidenav-dropdown2']) ?>
+
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/form.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/form.php
new file mode 100644
index 00000000..b946e935
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/form.php
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+ has(\'error\')) : ?>
+
+
+
+ t(\'common.the_image\')]) ?>
+
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/my-posts.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/my-posts.php
new file mode 100644
index 00000000..b830db29
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/my-posts.php
@@ -0,0 +1,25 @@
+
+
+
+
+ ...
+
+
+ t(\'common.the_post\')]) ?>
+
+ check()): ?>
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/back.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/back.php
new file mode 100644
index 00000000..96b90f02
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/back.php
@@ -0,0 +1,7 @@
+\">
+ arrow_back
+
+";
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/modal.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/modal.php
new file mode 100644
index 00000000..84878fa3
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/modal.php
@@ -0,0 +1,13 @@
+
+
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/my-post-item.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/my-post-item.php
new file mode 100644
index 00000000..ff63271c
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/my-post-item.php
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ edit
+
+
+ delete
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/post-item.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/post-item.php
new file mode 100644
index 00000000..d31ba9c9
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/partials/post-item.php
@@ -0,0 +1,31 @@
+
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/post.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/post.php
new file mode 100644
index 00000000..09cc4193
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/post.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+ $post]) ?>
+
+
+
+
+ ...
+
+
+ t(\'common.the_post\')]) ?>
+
+';
\ No newline at end of file
diff --git a/src/Libraries/Module/Templates/Demo/Web/Views/post/single.php b/src/Libraries/Module/Templates/Demo/Web/Views/post/single.php
new file mode 100644
index 00000000..898f975f
--- /dev/null
+++ b/src/Libraries/Module/Templates/Demo/Web/Views/post/single.php
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+';
\ No newline at end of file