Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba74c5a38e | ||
| c3e3d6a73a | |||
|
|
ae212a4377 | ||
|
|
696386bb06 | ||
| 3d50cfaccb | |||
|
|
c0b0681fee | ||
|
|
c9b107809d | ||
|
|
c5ec582bd1 | ||
|
|
5d6d7ff49c | ||
| 0e0f004bc1 | |||
|
|
2dbcfbcb2e | ||
| d181c6d3a7 | |||
|
|
4d7053a9cb | ||
|
|
3ce356e1f2 | ||
|
|
ad310ffee5 | ||
|
|
21f315cc22 |
@ -1,7 +0,0 @@
|
|||||||
class ApiConsole {
|
|
||||||
constructor(options) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -38,7 +38,8 @@
|
|||||||
"lean" : {
|
"lean" : {
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"definitions" : [
|
"definitions" : [
|
||||||
"meta/definitions/software.php"
|
"meta/definitions/software.php",
|
||||||
|
"meta/definitions/storage.php"
|
||||||
],
|
],
|
||||||
"config": [
|
"config": [
|
||||||
"meta/config.php"
|
"meta/config.php"
|
||||||
|
|||||||
@ -1,9 +1,28 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
$path = dirname(__DIR__, 1);
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'lean' => [
|
'lean' => [
|
||||||
'autoload' => [
|
'autoload' => [
|
||||||
'lean.api',
|
'lean.api',
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
];
|
|
||||||
|
'ulmus' => [
|
||||||
|
'connections' => [
|
||||||
|
'lean.api' => [
|
||||||
|
'adapter' => getenv("LEAN_API_ADAPTER") ?: "SQLite",
|
||||||
|
'path' => getenv('PROJECT_PATH') . DIRECTORY_SEPARATOR . ( getenv("LEAN_API_PATH") ?: "var/lean-api.sqlite3" ),
|
||||||
|
'pragma_begin' => array_merge(
|
||||||
|
explode(',', getenv("LEAN_API_PRAGMA_BEGIN") ?: "foreign_keys=ON,synchronous=NORMAL"),
|
||||||
|
explode(',', getenv('DEBUG') ? getenv("LEAN_API_PRAGMA_DEBUG_BEGIN") : "journal_mode=WAL")
|
||||||
|
),
|
||||||
|
'pragma_close' => array_merge(
|
||||||
|
explode(',', getenv("LEAN_API_PRAGMA_CLOSE") ?: "analysis_limit=500,optimize"),
|
||||||
|
explode(',', getenv('DEBUG') ? getenv("LEAN_API_PRAGMA_DEBUG_CLOSE") : "")
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
@ -1,6 +1,8 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$path = dirname(__DIR__, 2);
|
use Ulmus\ConnectionAdapter;
|
||||||
|
|
||||||
|
define('LEAN_API_PROJECT_PATH', $path = dirname(__DIR__, 2));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'lean.api' => [
|
'lean.api' => [
|
||||||
@ -22,6 +24,17 @@ return [
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'cli' => [
|
||||||
|
'Lean\\Api\\Cli' => implode(DIRECTORY_SEPARATOR, [ $path, "src", "Cli", "" ]),
|
||||||
|
],
|
||||||
|
|
||||||
|
'ulmus' => [
|
||||||
|
'entities' => [ 'Lean\\Api\\Entity' => implode(DIRECTORY_SEPARATOR, [ $path, 'src', 'Entity', '' ]) ],
|
||||||
|
'adapters' => [
|
||||||
|
DI\get('lean.api:storage'),
|
||||||
|
]
|
||||||
|
],
|
||||||
|
|
||||||
'tell' => [
|
'tell' => [
|
||||||
'json' => [
|
'json' => [
|
||||||
[
|
[
|
||||||
@ -31,8 +44,21 @@ return [
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
|
|
||||||
/*'routes' => [
|
'routes' => [
|
||||||
'Lean\\Api\\Controller' => implode(DIRECTORY_SEPARATOR, [ $path, "src", "Controller", "" ]),
|
'Lean\\Api\\Controller' => implode(DIRECTORY_SEPARATOR, [ $path, "src", "Controller", "" ]),
|
||||||
],*/
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
|
'lean.api:storage' => function($c) {
|
||||||
|
$adapter = new ConnectionAdapter('lean.api', $c->get('config')['ulmus'], false);
|
||||||
|
$adapter->resolveConfiguration();
|
||||||
|
|
||||||
|
return $adapter;
|
||||||
|
},
|
||||||
|
|
||||||
|
Lean\ApplicationStrategy\NotFoundDecoratorInterface::class => DI\autowire(Lean\Api\ApplicationStrategy\NotFoundDecorator::class),
|
||||||
|
Lean\ApplicationStrategy\MethodNotAllowedInterface::class => DI\autowire(Lean\Api\ApplicationStrategy\MethodNotAllowedDecorator::class),
|
||||||
|
Lean\Api\Factory\MessageFactoryInterface::class => DI\autowire(Lean\Api\Lib\Message::class),
|
||||||
|
Lean\Api\Factory\DebugFormFactoryInterface::class => DI\autowire(Lean\Api\Factory\DebugFormFactory::class),
|
||||||
|
# League\Route\Strategy\ApplicationStrategy::class => DI\autowire(Lean\Api\ApplicationStrategy::class),
|
||||||
];
|
];
|
||||||
19
meta/definitions/storage.php
Normal file
19
meta/definitions/storage.php
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
|
||||||
|
use Ulmus\ConnectionAdapter,
|
||||||
|
Ulmus\Container\AdapterProxy;
|
||||||
|
|
||||||
|
use Storage\Session;
|
||||||
|
|
||||||
|
use function DI\autowire, DI\create, DI\get;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'lean.api:storage' => function($c) {
|
||||||
|
$adapter = new ConnectionAdapter('lean.api', $c->get('config')['ulmus'], false);
|
||||||
|
$adapter->resolveConfiguration();
|
||||||
|
|
||||||
|
return $adapter;
|
||||||
|
},
|
||||||
|
];
|
||||||
21
meta/docs/debug.md
Normal file
21
meta/docs/debug.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# LEAN API
|
||||||
|
|
||||||
|
## Debug tooling to help development within Lean's API
|
||||||
|
|
||||||
|
[Documentation](../) / Debug API queries
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
{route:descriptor}
|
||||||
|
|
||||||
|
{request:debugger}
|
||||||
|
|
||||||
|
### Forms / actions
|
||||||
|
|
||||||
|
{form:descriptor}
|
||||||
|
|
||||||
|
### Entities
|
||||||
|
|
||||||
|
{entity:descriptor}
|
||||||
21
meta/docs/debug/errors.md
Normal file
21
meta/docs/debug/errors.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# LEAN API
|
||||||
|
|
||||||
|
## Debug tooling to help development within Lean's API
|
||||||
|
|
||||||
|
[Lean API](../../debug) / Debug API queries
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
{route:descriptor}
|
||||||
|
|
||||||
|
{request:debugger}
|
||||||
|
|
||||||
|
### Forms / actions
|
||||||
|
|
||||||
|
{form:descriptor}
|
||||||
|
|
||||||
|
### Entities
|
||||||
|
|
||||||
|
{entity:descriptor}
|
||||||
7
meta/docs/lean-api.md
Normal file
7
meta/docs/lean-api.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Lean API
|
||||||
|
|
||||||
|
## Lean's API internal debug API
|
||||||
|
|
||||||
|
### Routes disponibles
|
||||||
|
|
||||||
|
- [Debug / Errors](./debug/errors/documentation)
|
||||||
@ -1,11 +1,26 @@
|
|||||||
{
|
{
|
||||||
|
"descriptor": {
|
||||||
|
"form": {
|
||||||
|
"none": "Aucun formulaire existant pour cet API"
|
||||||
|
},
|
||||||
|
"entity": {
|
||||||
|
"none": "Aucune entité trouvée pour cet API"
|
||||||
|
},
|
||||||
|
"route": {
|
||||||
|
"none": "Aucune route trouvée pour cet API"
|
||||||
|
}
|
||||||
|
},
|
||||||
"form": {
|
"form": {
|
||||||
|
"error": {
|
||||||
|
"entity": "Une propriété $entity est nécessaire pour envoyer ce formulaire."
|
||||||
|
},
|
||||||
"save": {
|
"save": {
|
||||||
"error": {
|
"error": {
|
||||||
"entity": "Une propriété $entity est nécessaire dans ce form",
|
|
||||||
"save": "Une erreur est survenue en tenant de sauvegarder les données",
|
|
||||||
"pdo": "Une erreur est survenue : '{$error}'"
|
"pdo": "Une erreur est survenue : '{$error}'"
|
||||||
},
|
},
|
||||||
|
"warning": {
|
||||||
|
"entity": "Aucun changement apporté pour l'objet '{$entity}'."
|
||||||
|
},
|
||||||
"success": {
|
"success": {
|
||||||
"save": "Sauvegarde effectué avec succès"
|
"save": "Sauvegarde effectué avec succès"
|
||||||
}
|
}
|
||||||
|
|||||||
63
skeleton/EntityGenerate/Api.php
Normal file
63
skeleton/EntityGenerate/Api.php
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%API_NS%;
|
||||||
|
|
||||||
|
use Notes\Attribute\Ignore;
|
||||||
|
use Notes\Route\Attribute\Method\Route;
|
||||||
|
use Psr\Http\Message\{ ServerRequestInterface, ResponseInterface };
|
||||||
|
|
||||||
|
use %NAMESPACE%\{Lib, %USE_ENTITY_NS%, %USE_FORM_NS%, };
|
||||||
|
use function %NAMESPACE%\View\{_, lang, url, route, form};
|
||||||
|
|
||||||
|
#[\Notes\Route\Attribute\Object\Route(base:"%API_ROUTE_BASE%/%CLASSNAME_LC%")]
|
||||||
|
class %CLASSNAME% {
|
||||||
|
use Lib\ApiTrait;
|
||||||
|
|
||||||
|
#[Route(route: "/documentation", name: "api.%CLASSNAME_LC%:docs", method: "GET")]
|
||||||
|
public function index(ServerRequestInterface $request, array $attributes): ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->renderMarkdown(getenv("PROJECT_PATH") . "/meta/docs/%CLASSNAME_LC%.md", [ %ENTITY_NS%\%CLASSNAME%::class, ], [ %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%::class, %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%::class ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/", name: "api.%CLASSNAME_LC%:list", method: "GET")]
|
||||||
|
public function list(ServerRequestInterface $request, array $attributes) : ResponseInterface
|
||||||
|
{
|
||||||
|
$request = $this->searchEntitiesFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
|
||||||
|
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
|
||||||
|
|
||||||
|
return $this->outputSearchResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/", name: "api.%CLASSNAME_LC%:add", method: "POST")]
|
||||||
|
public function add(ServerRequestInterface $request, array $attributes) : ResponseInterface
|
||||||
|
{
|
||||||
|
$entity = new %ENTITY_NS%\%CLASSNAME%();
|
||||||
|
|
||||||
|
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request));
|
||||||
|
|
||||||
|
return $this->output($entity, (int) $entity->isLoaded());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:single", method: "GET")]
|
||||||
|
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:edit", method: "PATCH")]
|
||||||
|
public function single(ServerRequestInterface $request, array $attributes) : ResponseInterface
|
||||||
|
{
|
||||||
|
$request = $this->searchEntityFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
|
||||||
|
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
|
||||||
|
|
||||||
|
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($result->getResult()), $this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request));
|
||||||
|
|
||||||
|
return $this->outputSearchResult($result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/{id:\d+}", name: "api.%CLASSNAME_LC%:delete", method: "DELETE")]
|
||||||
|
public function delete(ServerRequestInterface $request, array $attributes) : ResponseInterface
|
||||||
|
{
|
||||||
|
$request = $this->searchEntityFromRequest($request, %ENTITY_NS%\%CLASSNAME%::class);
|
||||||
|
$result = $request->getAttribute("lean.searchRequest")[%ENTITY_NS%\%CLASSNAME%::class];
|
||||||
|
|
||||||
|
form($this->formFactory->%DELETE_FORM_CLASSNAME_LC%($result->getResult()), $this->formFactory->%DELETE_FORM_CONTEXT_CLASSNAME_LC%($request));
|
||||||
|
|
||||||
|
return $this->outputSearchResult($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
skeleton/EntityGenerate/Controller.php
Normal file
66
skeleton/EntityGenerate/Controller.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%WEB_NS%;
|
||||||
|
|
||||||
|
use Notes\Breadcrumb\Attribute\Method\Breadcrumb;
|
||||||
|
use Notes\Route\Attribute\Method\Route;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface,
|
||||||
|
Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
use %NAMESPACE%\{Lib, %USE_ENTITY_NS%, %USE_FORM_NS%, };
|
||||||
|
|
||||||
|
use function %NAMESPACE%\View\{_, lang, url, route, form};
|
||||||
|
|
||||||
|
#[\Notes\Route\Attribute\Object\Route(base:"%WEB_ROUTE_BASE%/%CLASSNAME_LC%")]
|
||||||
|
class %CLASSNAME% {
|
||||||
|
use Lib\ControllerTrait;
|
||||||
|
|
||||||
|
#[Route("/", name: "%CLASSNAME_LC%:index")]
|
||||||
|
##[Breadcrumb(parent: "", icon: "", lang: "app.lang.breadcrumb")]
|
||||||
|
public function index(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request) ;
|
||||||
|
$list = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadAll();
|
||||||
|
|
||||||
|
return $this->renderView('%CLASSNAME_LC%/index', get_defined_vars());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/create", name: "%CLASSNAME_LC%:create", method: ["GET", "POST"])]
|
||||||
|
##[Breadcrumb(parent: "%CLASSNAME_LC%:index", icon: "", lang: "app.lang.breadcrumb")]
|
||||||
|
public function create(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
$entity = new %ENTITY_NS%\%CLASSNAME%();
|
||||||
|
|
||||||
|
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.create")));
|
||||||
|
|
||||||
|
if ($entity->isLoaded()) {
|
||||||
|
return $this->redirect(route("%CLASSNAME_LC%:edit", [ 'id' => $entity->id ]) );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderView('%CLASSNAME_LC%/create', get_defined_vars());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/{id:\d+}/edit", name: "%CLASSNAME_LC%:edit", method: ["GET", "POST"])]
|
||||||
|
##[Breadcrumb(parent: "%CLASSNAME_LC%:index", icon: "", lang: "app.lang.breadcrumb")]
|
||||||
|
public function edit(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request);
|
||||||
|
$entity = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadOne();
|
||||||
|
|
||||||
|
form($this->formFactory->%SAVE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%SAVE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.edit")));
|
||||||
|
|
||||||
|
return $this->renderView('%CLASSNAME_LC%/edit', get_defined_vars());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/{id:\d+}/delete", name: "%CLASSNAME_LC%:delete", method: ["POST", "DELETE"])]
|
||||||
|
public function delete(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
$search = %ENTITY_NS%\%CLASSNAME%::searchRequest()->fromRequest($request);
|
||||||
|
$entity = %ENTITY_NS%\%CLASSNAME%::repository()->filterServerRequest($search)->loadOne();
|
||||||
|
|
||||||
|
form($this->formFactory->%DELETE_FORM_CLASSNAME_LC%($entity), $this->pushContext($this->formFactory->%DELETE_FORM_CONTEXT_CLASSNAME_LC%($request, "%CLASSNAME_LC%.delete")));
|
||||||
|
|
||||||
|
return $this->redirect(route("%CLASSNAME_LC%:index"));
|
||||||
|
}
|
||||||
|
}
|
||||||
20
skeleton/EntityGenerate/DeleteForm.php
Normal file
20
skeleton/EntityGenerate/DeleteForm.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%FORM_NS%;
|
||||||
|
|
||||||
|
use Picea\Ui\Method\{ FormContextInterface };
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
|
||||||
|
use %NAMESPACE%\{%USE_ENTITY_NS%, };
|
||||||
|
|
||||||
|
class %DELETE_FORM_CLASSNAME% extends \Lean\Api\Form\Delete
|
||||||
|
{
|
||||||
|
protected string $contextClass = %DELETE_FORM_CONTEXT_CLASSNAME%::class;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $message,
|
||||||
|
protected %ENTITY_NS%\%CLASSNAME% $entity,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
22
skeleton/EntityGenerate/DeleteFormContext.php
Normal file
22
skeleton/EntityGenerate/DeleteFormContext.php
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%FORM_NS%;
|
||||||
|
|
||||||
|
use Lean\Api\Attribute\ContextField;
|
||||||
|
use %NAMESPACE%\{ %USE_ENTITY_NS% };
|
||||||
|
|
||||||
|
#[ContextField]
|
||||||
|
class %DELETE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext
|
||||||
|
{
|
||||||
|
# #[ContextField]
|
||||||
|
# public string $name;
|
||||||
|
|
||||||
|
public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool
|
||||||
|
{
|
||||||
|
if ( $entity === null ) {
|
||||||
|
$this->pushErrorMessage('lean.api.form.delete.error.entity');
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::valid();
|
||||||
|
}
|
||||||
|
}
|
||||||
43
skeleton/EntityGenerate/Entity.php
Normal file
43
skeleton/EntityGenerate/Entity.php
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%ENTITY_NS%;
|
||||||
|
|
||||||
|
use Ulmus\{
|
||||||
|
Attribute\IndexTypeEnum,
|
||||||
|
Attribute\Obj\Table,
|
||||||
|
Attribute\Property\Field,
|
||||||
|
Attribute\Property\Index,
|
||||||
|
Attribute\Property\OrderBy,
|
||||||
|
Attribute\Property\Relation,
|
||||||
|
Attribute\Property\Virtual,
|
||||||
|
Entity\EntityInterface,
|
||||||
|
EntityCollection,
|
||||||
|
Entity\Field\Datetime,
|
||||||
|
Repository,
|
||||||
|
SearchRequest,
|
||||||
|
};
|
||||||
|
|
||||||
|
use Ulmus\SearchRequest\Attribute\SearchWhere;
|
||||||
|
use Notes\Attribute\Ignore;
|
||||||
|
|
||||||
|
use %NAMESPACE%\Lib;
|
||||||
|
|
||||||
|
#[Table(name: "%CLASSNAME_LC%")]
|
||||||
|
class %CLASSNAME% implements \JsonSerializable, EntityInterface
|
||||||
|
{
|
||||||
|
use Lib\EntityTrait;
|
||||||
|
|
||||||
|
#[Field\Id]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
|
public static function searchRequest(...$arguments) : SearchRequest\SearchRequestInterface
|
||||||
|
{
|
||||||
|
return new #[SearchRequest\Attribute\SearchRequestParameter(%CLASSNAME%::class)] class(... $arguments) extends SearchRequest\SearchRequest {
|
||||||
|
|
||||||
|
#[SearchWhere(parameter: [ '%CLASSNAME_LC%_id', 'id' ], source: SearchRequest\Attribute\PropertyValueSource::RequestAttribute)]
|
||||||
|
public int $id;
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
20
skeleton/EntityGenerate/FactoryAppend.php
Normal file
20
skeleton/EntityGenerate/FactoryAppend.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%SAVE_FORM_CLASSNAME%($this->languageHandler, $this->message, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->message, $request, $formName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%DELETE_FORM_CLASSNAME%($this->languageHandler, $this->message, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->message, $request, $formName);
|
||||||
|
}
|
||||||
|
}
|
||||||
5
skeleton/EntityGenerate/FactoryInterfaceAppend.php
Normal file
5
skeleton/EntityGenerate/FactoryInterfaceAppend.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface;
|
||||||
|
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface;
|
||||||
|
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface;
|
||||||
|
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface;
|
||||||
|
}
|
||||||
32
skeleton/EntityGenerate/FactoryTrait.php
Normal file
32
skeleton/EntityGenerate/FactoryTrait.php
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%FACTORY_TRAIT_NS%;
|
||||||
|
|
||||||
|
use %NAMESPACE%\{ %USE_ENTITY_NS%, %USE_FORM_NS%, };
|
||||||
|
use Picea\Ui\Method\FormContextInterface;
|
||||||
|
use Picea\Ui\Method\FormInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
|
||||||
|
trait %CLASSNAME%Trait
|
||||||
|
{
|
||||||
|
public function %SAVE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%SAVE_FORM_CLASSNAME%($this->languageHandler, $this->messageFactory, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %SAVE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%SAVE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->messageFactory, $request, $formName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %DELETE_FORM_CLASSNAME_LC%(EntityInterface|%ENTITY_NS%\%CLASSNAME% $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%DELETE_FORM_CLASSNAME%($this->languageHandler, $this->messageFactory, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function %DELETE_FORM_CONTEXT_CLASSNAME_LC%(ServerRequestInterface $request, ? string $formName = ''): FormContextInterface
|
||||||
|
{
|
||||||
|
return new %FORM_NS%\%DELETE_FORM_CONTEXT_CLASSNAME%($this->languageHandler, $this->messageFactory, $request, $formName);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
skeleton/EntityGenerate/SaveForm.php
Normal file
20
skeleton/EntityGenerate/SaveForm.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%FORM_NS%;
|
||||||
|
|
||||||
|
use Picea\Ui\Method\{ FormContextInterface };
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
|
||||||
|
use %NAMESPACE%\{%USE_ENTITY_NS%, };
|
||||||
|
|
||||||
|
class %SAVE_FORM_CLASSNAME% extends \Lean\Api\Form\Save
|
||||||
|
{
|
||||||
|
protected string $contextClass = %SAVE_FORM_CONTEXT_CLASSNAME%::class;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $message,
|
||||||
|
protected %ENTITY_NS%\%CLASSNAME% $entity,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
18
skeleton/EntityGenerate/SaveFormContext.php
Normal file
18
skeleton/EntityGenerate/SaveFormContext.php
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace %NAMESPACE%\%FORM_NS%;
|
||||||
|
|
||||||
|
use Lean\Api\Attribute\ContextField;
|
||||||
|
use %NAMESPACE%\{ %USE_ENTITY_NS% };
|
||||||
|
|
||||||
|
#[ContextField]
|
||||||
|
class %SAVE_FORM_CONTEXT_CLASSNAME% extends \Lean\Api\Lib\FormContext
|
||||||
|
{
|
||||||
|
# #[ContextField]
|
||||||
|
# public string $name;
|
||||||
|
|
||||||
|
public function valid(? %ENTITY_NS%\%CLASSNAME% $entity = null) : bool
|
||||||
|
{
|
||||||
|
return parent::valid();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
skeleton/EntityGenerate/api-docs.md
Normal file
21
skeleton/EntityGenerate/api-docs.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Title
|
||||||
|
|
||||||
|
## API 1.0
|
||||||
|
|
||||||
|
[APP](../) / %CLASSNAME%
|
||||||
|
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
### Routes
|
||||||
|
|
||||||
|
{route:descriptor}
|
||||||
|
|
||||||
|
{request:debugger}
|
||||||
|
|
||||||
|
### Forms
|
||||||
|
|
||||||
|
{form:descriptor}
|
||||||
|
|
||||||
|
### Entities
|
||||||
|
|
||||||
|
{entity:descriptor}
|
||||||
599
src/Action/Console/EntityGenerate.php
Normal file
599
src/Action/Console/EntityGenerate.php
Normal file
@ -0,0 +1,599 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Action\Console;
|
||||||
|
|
||||||
|
use Lean\Application;
|
||||||
|
use Lean\Composer;
|
||||||
|
use Mcnd\CLI\Terminal;
|
||||||
|
|
||||||
|
class EntityGenerate
|
||||||
|
{
|
||||||
|
# public string $applicationPath;
|
||||||
|
public string $namespace;
|
||||||
|
public string $entityNamespace;
|
||||||
|
public string $formNamespace;
|
||||||
|
public string $saveForm;
|
||||||
|
public string $saveContext;
|
||||||
|
public string $deleteForm;
|
||||||
|
public string $deleteContext;
|
||||||
|
public string $apiNamespace;
|
||||||
|
public string $apiBase;
|
||||||
|
public string $webNamespace;
|
||||||
|
public string $webBase;
|
||||||
|
public string $factoryAction;
|
||||||
|
public string $factoryNamespace;
|
||||||
|
public string $factoryTraitNamespace;
|
||||||
|
public string $factoryFile;
|
||||||
|
public string $factoryInterfaceFile;
|
||||||
|
public bool $includeDocs = false;
|
||||||
|
|
||||||
|
public readonly string $compiledEntity;
|
||||||
|
public readonly string $compiledSaveForm;
|
||||||
|
public readonly string $compiledSaveFormContext;
|
||||||
|
public readonly string $compiledDeleteForm;
|
||||||
|
public readonly string $compiledDeleteFormContext;
|
||||||
|
public readonly string $compiledFactory;
|
||||||
|
public readonly string $compiledFactoryTrait;
|
||||||
|
public readonly string $compiledFactoryInterface;
|
||||||
|
public readonly string $compiledApi;
|
||||||
|
public readonly string $compiledWeb;
|
||||||
|
public readonly string $compiledDocs;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
public Application $application,
|
||||||
|
public string $name,
|
||||||
|
public bool $overwrite,
|
||||||
|
public bool $default,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function writeToDisc() : void
|
||||||
|
{
|
||||||
|
$entityPath = $this->pathFromNamespace($this->entityNamespace, $this->application->entities);
|
||||||
|
|
||||||
|
if (empty($entityPath)) {
|
||||||
|
throw new \InvalidArgumentException("Could not find entities path from given namespaces.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->webNamespace)) {
|
||||||
|
$webPath = $this->pathFromNamespace($this->webNamespace, $this->application->routes);
|
||||||
|
|
||||||
|
if (empty($webPath)) {
|
||||||
|
throw new \InvalidArgumentException("Could not find web path routes from given namespaces.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->apiNamespace)) {
|
||||||
|
$apiPath = $this->pathFromNamespace($this->apiNamespace, $this->application->routes);
|
||||||
|
|
||||||
|
if (empty($apiPath)) {
|
||||||
|
throw new \InvalidArgumentException("Could not find web path routes from given namespaces.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->includeDocs) {
|
||||||
|
$docPath = implode(DIRECTORY_SEPARATOR, [ getenv('META_PATH'), "docs" ]);
|
||||||
|
$docPath = $this->readline(sprintf("Path of API's documentation output (default: %s) : ", $docPath), $docPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->formNamespace)) {
|
||||||
|
$formPath = implode(DIRECTORY_SEPARATOR, [ $this->pathFromPSR($this->formNamespace), $this->formNamespace ]);
|
||||||
|
$formPath = $this->readline(sprintf("Path of Forms (default: %s) : ", $formPath), $formPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->factoryNamespace)) {
|
||||||
|
$factoryPath = implode(DIRECTORY_SEPARATOR, [ $this->pathFromPSR($this->factoryNamespace), $this->factoryNamespace ]);
|
||||||
|
$factoryPath = $this->readline(sprintf("Path of Factory (default: %s) : ", $factoryPath), $factoryPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( Terminal::proposeListChoice("Input completed, write to files ?", [ true => 'Yes', false => 'No', ]) ) {
|
||||||
|
$this->writeToFile($entityPath, $this->name, $this->compiledEntity, );
|
||||||
|
|
||||||
|
if (isset($webPath)) {
|
||||||
|
$this->writeToFile($webPath, $this->name, $this->compiledWeb, );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($apiPath)) {
|
||||||
|
$this->writeToFile($apiPath, $this->name, $this->compiledApi, );
|
||||||
|
|
||||||
|
if ($this->includeDocs) {
|
||||||
|
$this->writeToFile($docPath , strtolower($this->name), $this->compiledDocs, ".md");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($formPath)) {
|
||||||
|
if ($this->saveForm) {
|
||||||
|
$this->writeToFile($formPath, $this->saveForm, $this->compiledSaveForm,);
|
||||||
|
$this->writeToFile($formPath, $this->saveContext, $this->compiledSaveFormContext,);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->deleteForm) {
|
||||||
|
$this->writeToFile($formPath, $this->deleteForm, $this->compiledDeleteForm,);
|
||||||
|
$this->writeToFile($formPath, $this->deleteContext, $this->compiledDeleteFormContext,);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->factoryNamespace)) {
|
||||||
|
if (isset($this->factoryTraitNamespace)) {
|
||||||
|
$traitName = $this->name . "Trait";
|
||||||
|
|
||||||
|
$this->writeToFile(implode(DIRECTORY_SEPARATOR, [$factoryPath, "Factories"]), $traitName, $this->compiledFactoryTrait,);
|
||||||
|
$this->appendToFile($factoryPath, $this->factoryFile, "\tuse Factories\\$traitName;\n}", "");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->appendToFile($factoryPath, $this->factoryFile, $this->compiledFactory, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->appendToFile($factoryPath, $this->factoryInterfaceFile, $this->compiledFactoryInterface, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function appendToFile(string $path, string $filename, string $content, string $extension = ".php"): void
|
||||||
|
{
|
||||||
|
$fullpath = implode(DIRECTORY_SEPARATOR, [ rtrim($path, DIRECTORY_SEPARATOR), $filename . "$extension" ]);
|
||||||
|
|
||||||
|
$existingFileContent = file_get_contents($fullpath);
|
||||||
|
|
||||||
|
$this->writeToFile($path, $filename, rtrim($existingFileContent, '} ') . $content, $extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function writeToFile(string $path, string $filename, string $content, string $extension = ".php"): void
|
||||||
|
{
|
||||||
|
$fullpath = implode(DIRECTORY_SEPARATOR, [ rtrim($path, DIRECTORY_SEPARATOR), $filename . "$extension" ]);
|
||||||
|
|
||||||
|
if (file_exists($fullpath)) {
|
||||||
|
if ((! $this->overwrite) && Terminal::proposeListChoice(sprintf("Trying to write over an existing file '%s',\nOverwrite file ?", $fullpath), [ false => 'Yes', true => 'No', ])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
elseif (! file_exists(dirname($fullpath))) {
|
||||||
|
mkdir(dirname($fullpath), 0755, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($fullpath, $content, \LOCK_EX);
|
||||||
|
|
||||||
|
Terminal::sprintfline("File successfully written '%s'", $fullpath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function pathFromNamespace(string $namespace, array $source) : false|string
|
||||||
|
{
|
||||||
|
$split = explode('\\', $namespace);
|
||||||
|
|
||||||
|
while($split) {
|
||||||
|
$ns = $this->namespace . "\\" . implode('\\', $split);
|
||||||
|
|
||||||
|
if ( isset($source[$ns]) ) {
|
||||||
|
return rtrim($source[$ns], DIRECTORY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
array_pop($split);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function pathFromPSR(string $namespace) : false|string
|
||||||
|
{
|
||||||
|
$source = $this->readComposerPsr4();
|
||||||
|
$split = explode('\\', $namespace);
|
||||||
|
|
||||||
|
while($split) {
|
||||||
|
$ns = $this->namespace . "\\" . implode('\\', $split);
|
||||||
|
|
||||||
|
if (! str_ends_with($ns, '\\')) {
|
||||||
|
$ns .= "\\";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( isset($source[$ns]) ) {
|
||||||
|
return $source[$ns][0];
|
||||||
|
}
|
||||||
|
|
||||||
|
array_pop($split);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $source[$this->namespace . "\\"][0] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function outputResults() : void
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readline(? string $prompt, mixed $default = null): string|false
|
||||||
|
{
|
||||||
|
$result = Terminal::readline($prompt);
|
||||||
|
|
||||||
|
if ($this->default && $default !== null) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result ?: $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readNamespace() : void
|
||||||
|
{
|
||||||
|
$namespace = $this->readComposerNamespace();
|
||||||
|
$this->namespace = $this->readline(sprintf('Namespace (default: %s) : ', $namespace), $namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readApi() : void
|
||||||
|
{
|
||||||
|
$this->apiNamespace = $this->readline('API namespace (default: Api\\) : ', 'Api');
|
||||||
|
$this->apiBase = $this->readline('API URL base (default: /api) : ', '/api');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readWeb() : void
|
||||||
|
{
|
||||||
|
$this->webNamespace = $this->readline('Controller namespace (default: Controller\\) : ', 'Controller');
|
||||||
|
$this->webBase = $this->readline('Controller URL base (default: /) : ', "");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readEntity() : void
|
||||||
|
{
|
||||||
|
$this->entityNamespace = $this->readline('Entity namespace (default: Entity\\) : ', 'Entity');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readFactory() : void
|
||||||
|
{
|
||||||
|
$this->factoryNamespace ??= $this->readline('Factory namespace (default: Factory\\) : ', 'Factory');
|
||||||
|
$this->factoryFile = $this->readline('Form factory interface file (default: FormFactory.php) : ', 'FormFactory.php');
|
||||||
|
$this->factoryInterfaceFile = $this->readline('Form factory interface file (default: FormFactoryInterface.php) : ', 'FormFactoryInterface.php');
|
||||||
|
|
||||||
|
$this->factoryAction = Terminal::proposeListChoice("Use new trait or append to file ?", [ 'trait' => 'Use trait', 'append' => 'Append to file', ]);
|
||||||
|
|
||||||
|
if ($this->factoryAction === 'trait') {
|
||||||
|
$this->factoryTraitNamespace = implode('\\', [ $this->factoryNamespace, 'Factories' ]);
|
||||||
|
$this->factoryTraitNamespace = $this->readline(sprintf("Factory trait namespace (default: %s) : ", $this->factoryTraitNamespace), $this->factoryTraitNamespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readSaveForm() : void
|
||||||
|
{
|
||||||
|
$this->formNamespace ??= $this->readline('Form namespace (default: Form\\) : ', 'Form');
|
||||||
|
|
||||||
|
$this->saveForm = ucfirst($this->name) . "Save";
|
||||||
|
$this->saveForm = $this->readline(sprintf('Form "create/update" name (default: %s) : ', $this->saveForm), $this->saveForm);
|
||||||
|
|
||||||
|
$this->saveContext = $this->saveForm . "Context";
|
||||||
|
$this->saveContext = $this->readline(sprintf('Context "create/update" (default: %s) : ', $this->saveContext), $this->saveContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function readDeleteForm() : void
|
||||||
|
{
|
||||||
|
$this->formNamespace ??= $this->readline('Form namespace (default: Form\\) : ') ?: 'Form';
|
||||||
|
|
||||||
|
$this->deleteForm = ucfirst($this->name) . "Delete";
|
||||||
|
$this->deleteForm = $this->readline(sprintf('Form "delete" name (default: %s) : ', $this->deleteForm), $this->deleteForm);
|
||||||
|
|
||||||
|
$this->deleteContext = $this->deleteForm . "Context";
|
||||||
|
$this->deleteContext = $this->readline(sprintf('Context "delete" (default: %s) : ', $this->deleteContext), $this->deleteContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public function readApplicationPath(string $default) : void
|
||||||
|
{
|
||||||
|
$this->applicationPath = $this->readline(sprintf('Application path base (default: %s) : ', $default)) ?: $default;
|
||||||
|
}*/
|
||||||
|
|
||||||
|
public function generate() : void
|
||||||
|
{
|
||||||
|
$this->namespace = trim($this->namespace, '\\');
|
||||||
|
$this->formNamespace = trim($this->formNamespace, '\\');
|
||||||
|
$this->entityNamespace = trim($this->entityNamespace, '\\');
|
||||||
|
|
||||||
|
$this->compiledEntity = $this->defineEntityBoilerplate();
|
||||||
|
|
||||||
|
if (isset($this->saveForm)) {
|
||||||
|
$this->compiledSaveForm = $this->defineSaveFormBoilerplate();
|
||||||
|
$this->compiledSaveFormContext = $this->defineSaveFormContextBoilerplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->deleteForm)) {
|
||||||
|
$this->compiledDeleteForm = $this->defineDeleteFormBoilerplate();
|
||||||
|
$this->compiledDeleteFormContext = $this->defineDeleteFormContextBoilerplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->apiNamespace)) {
|
||||||
|
$this->compiledApi = $this->defineApiBoilerplate();
|
||||||
|
|
||||||
|
if ($this->includeDocs) {
|
||||||
|
$this->compiledDocs = $this->defineDocsBoilerplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->webNamespace)) {
|
||||||
|
$this->compiledWeb = $this->defineWebBoilerplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->factoryNamespace)) {
|
||||||
|
if (isset($this->factoryTraitNamespace)) {
|
||||||
|
$this->compiledFactoryTrait = $this->defineFactoryTraitBoilerplate();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->compiledFactory = $this->defineFactoryBoilerplate();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->compiledFactoryInterface = $this->defineFactoryInterfaceBoilerplate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineEntityBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Entity'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineSaveFormBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('SaveForm'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineSaveFormContextBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('SaveFormContext'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineDeleteFormBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('DeleteForm'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineDeleteFormContextBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('DeleteFormContext'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineFactoryInterfaceBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryInterfaceAppend'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineFactoryTraitBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryTrait'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineFactoryBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('FactoryAppend'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineApiBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Api'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineDocsBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('api-docs', '.md'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function defineWebBoilerplate() : string
|
||||||
|
{
|
||||||
|
return $this->replaceBoilerplateCode($this->getBoilerplateFile('Controller'));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getBoilerplateFile(string $fileName, string $extension = ".php") : string
|
||||||
|
{
|
||||||
|
$path = implode(DIRECTORY_SEPARATOR, [ LEAN_API_PROJECT_PATH , 'skeleton', 'EntityGenerate', "{$fileName}{$extension}" ]);
|
||||||
|
$bp = file_get_contents($path);
|
||||||
|
|
||||||
|
if (! $bp ) {
|
||||||
|
throw new \InvalidArgumentException("Boilerplate file not found in path " . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getEntityBoilerplate() : string
|
||||||
|
{
|
||||||
|
$path = implode(DIRECTORY_SEPARATOR, [ LEAN_API_PROJECT_PATH , 'skeleton', 'EntityGenerate', 'Entity.php' ]);
|
||||||
|
$bp = file_get_contents($path);
|
||||||
|
|
||||||
|
if (! $bp ) {
|
||||||
|
throw new \InvalidArgumentException("Entity boilerplate file not found in path " . $path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $bp;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceBoilerplateCode(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
# to be reworked with the |> operator in 8.5+
|
||||||
|
$boilerplate = $this->replaceNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceClassname($boilerplate);
|
||||||
|
$boilerplate = $this->replaceEntityNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceFormNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceSaveFormClassname($boilerplate);
|
||||||
|
$boilerplate = $this->replaceSaveFormContextClassname($boilerplate);
|
||||||
|
$boilerplate = $this->replaceDeleteFormClassname($boilerplate);
|
||||||
|
$boilerplate = $this->replaceDeleteFormContextClassname($boilerplate);
|
||||||
|
$boilerplate = $this->replaceFactoryNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceFactoryTraitNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceApiNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceApiBase($boilerplate);
|
||||||
|
$boilerplate = $this->replaceWebNamespace($boilerplate);
|
||||||
|
$boilerplate = $this->replaceWebBase($boilerplate);
|
||||||
|
|
||||||
|
return $boilerplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return str_replace('%NAMESPACE%', $this->namespace, $boilerplate);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceEntityNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->entityNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%USE_ENTITY_NS%', '%ENTITY_NS%' ],
|
||||||
|
[ strstr($this->entityNamespace, '\\', true) ?: $this->entityNamespace, $this->entityNamespace ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceSaveFormClassname(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->saveForm ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%SAVE_FORM_CLASSNAME%', '%SAVE_FORM_CLASSNAME_LC%' ],
|
||||||
|
[ $this->saveForm, lcfirst($this->saveForm) ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceSaveFormContextClassname(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->saveContext ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%SAVE_FORM_CONTEXT_CLASSNAME%', '%SAVE_FORM_CONTEXT_CLASSNAME_LC%' ],
|
||||||
|
[ $this->saveContext, lcfirst($this->saveContext) ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceDeleteFormClassname(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->deleteForm ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%DELETE_FORM_CLASSNAME%', '%DELETE_FORM_CLASSNAME_LC%' ],
|
||||||
|
[ $this->deleteForm, lcfirst($this->deleteForm) ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceDeleteFormContextClassname(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->deleteContext ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%DELETE_FORM_CONTEXT_CLASSNAME%', '%DELETE_FORM_CONTEXT_CLASSNAME_LC%' ],
|
||||||
|
[ $this->deleteContext, lcfirst($this->deleteContext) ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceFormNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->formNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%USE_FORM_NS%', '%FORM_NS%', ],
|
||||||
|
[ strstr($this->formNamespace, '\\', true) ?: $this->formNamespace, $this->formNamespace ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceApiNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->apiNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%API_NS%', ],
|
||||||
|
[ rtrim($this->apiNamespace, '\\') ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceApiBase(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->apiBase ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%API_ROUTE_BASE%', ],
|
||||||
|
[ $this->apiBase ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceWebNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->webNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%WEB_NS%', ],
|
||||||
|
[ rtrim($this->webNamespace, '\\') ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceWebBase(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->webBase ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%WEB_ROUTE_BASE%', ],
|
||||||
|
[ $this->webBase ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceFactoryNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->factoryNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%FACTORY_NS%', ],
|
||||||
|
[ rtrim($this->factoryNamespace, '\\') ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceFactoryTraitNamespace(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->factoryTraitNamespace ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%FACTORY_TRAIT_NS%', ],
|
||||||
|
[ rtrim($this->factoryTraitNamespace, '\\') ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function replaceClassname(string $boilerplate) : string
|
||||||
|
{
|
||||||
|
return match($this->apiBase ?? false) {
|
||||||
|
false => $boilerplate,
|
||||||
|
default => str_replace(
|
||||||
|
[ '%CLASSNAME_LC%', '%CLASSNAME%' ],
|
||||||
|
[ strtolower(preg_replace('/([a-z])([A-Z])/', '$1_$2', $this->name)), $this->name ],
|
||||||
|
$boilerplate
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function readComposerNamespace() : ? string
|
||||||
|
{
|
||||||
|
if ( false !== $composerJson = Composer::readComposerJson() ) {
|
||||||
|
if ($psr4 = $composerJson['autoload']['psr-4'] ?? false) {
|
||||||
|
foreach ($psr4 as $ns => $directory) {
|
||||||
|
if ($directory === 'src/') {
|
||||||
|
return $ns;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function readComposerPsr4() : array
|
||||||
|
{
|
||||||
|
return include(implode(DIRECTORY_SEPARATOR, [ getenv('PROJECT_PATH'), 'vendor' , 'composer', 'autoload_psr4.php' ]));
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/ApplicationStrategy.php
Normal file
26
src/ApplicationStrategy.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api;
|
||||||
|
|
||||||
|
use League\Route\Strategy;
|
||||||
|
|
||||||
|
use League\Route\Http\Exception\NotFoundException;
|
||||||
|
use Lean\ApplicationStrategy\NotFoundDecoratorInterface;
|
||||||
|
use Lean\ApplicationStrategy\ThrowableHandler;
|
||||||
|
use Lean\Factory\HttpFactoryInterface;
|
||||||
|
use Picea\Asset\Asset;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Picea\Picea;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use function DI\get;
|
||||||
|
|
||||||
|
class ApplicationStrategy extends \Lean\ApplicationStrategy {
|
||||||
|
|
||||||
|
public function getNotFoundDecorator(NotFoundException $exception): MiddlewareInterface
|
||||||
|
{
|
||||||
|
return $this->getContainer()->get(NotFoundDecoratorInterface::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/ApplicationStrategy/MethodNotAllowedDecorator.php
Normal file
29
src/ApplicationStrategy/MethodNotAllowedDecorator.php
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\ApplicationStrategy;
|
||||||
|
|
||||||
|
use DI\Attribute\Inject;
|
||||||
|
use Lean\ApplicationStrategy;
|
||||||
|
use Lean\Factory\HttpFactoryInterface;
|
||||||
|
use Picea\Picea;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class MethodNotAllowedDecorator implements \Lean\ApplicationStrategy\MethodNotAllowedInterface
|
||||||
|
{
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
if ($request->getMethod() === 'OPTIONS') {
|
||||||
|
header('Access-Control-Allow-Origin: *');
|
||||||
|
header('Access-Control-Allow-Methods: *');
|
||||||
|
header('Access-Control-Allow-Headers: *');
|
||||||
|
header('Access-Control-Allow-Credentials: true');
|
||||||
|
exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler->handle($request);;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
src/ApplicationStrategy/NotFoundDecorator.php
Normal file
34
src/ApplicationStrategy/NotFoundDecorator.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\ApplicationStrategy;
|
||||||
|
|
||||||
|
use DI\Attribute\Inject;
|
||||||
|
use Lean\Api\Middleware\ApiRenderer;
|
||||||
|
use Lean\Factory\HttpFactoryInterface;
|
||||||
|
use Picea\Picea;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class NotFoundDecorator extends \Lean\ApplicationStrategy\NotFoundDecorator
|
||||||
|
{
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
if (php_sapi_name() !== 'cli' and ! defined('STDIN') and ApiRenderer::awaitingJson($request)) {
|
||||||
|
return $this->throw404api($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent::process($request, $handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function throw404api(ServerRequestInterface $request) : ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->checkAssetTrigger($request) ?: $this->httpFactory->createJsonResponse([
|
||||||
|
'status' => 'error',
|
||||||
|
'message' => "Not Found",
|
||||||
|
'ts' => time(),
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/ApplicationStrategy/ThrowableHandler.php
Normal file
23
src/ApplicationStrategy/ThrowableHandler.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\ApplicationStrategy;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class ThrowableHandler extends \Lean\ApplicationStrategy\ThrowableHandler
|
||||||
|
{
|
||||||
|
public function process(
|
||||||
|
ServerRequestInterface $request,
|
||||||
|
RequestHandlerInterface $handler
|
||||||
|
): ResponseInterface {
|
||||||
|
try {
|
||||||
|
return $handler->handle($request);
|
||||||
|
} catch (\Throwable $ex) {
|
||||||
|
echo sprintf("%s in <strong>%s</strong> <pre>%s</pre>", $ex->getMessage(), $ex->getFile() . ":" . $ex->getLine(), $ex->getTraceAsString());
|
||||||
|
exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
namespace Lean\Api\Attribute;
|
namespace Lean\Api\Attribute;
|
||||||
|
|
||||||
|
use Lean\Api\Exception\MandatoryFieldException;
|
||||||
|
use Lean\Api\Exception\MaximumLengthException;
|
||||||
|
use Lean\Api\Exception\MinimumLengthException;
|
||||||
|
use Lean\Api\Exception\RegexFormatException;
|
||||||
|
|
||||||
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)]
|
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_CLASS)]
|
||||||
class ContextField
|
class ContextField
|
||||||
{
|
{
|
||||||
@ -12,5 +17,32 @@ class ContextField
|
|||||||
public ?int $maxLength = null,
|
public ?int $maxLength = null,
|
||||||
public ?string $regexFormat = null,
|
public ?string $regexFormat = null,
|
||||||
public ?string $example = null,
|
public ?string $example = null,
|
||||||
|
public mixed $default = null,
|
||||||
|
public bool $hidden = false,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public function assertValueSpecs(mixed $value, bool $isNew) : void
|
||||||
|
{
|
||||||
|
if ($isNew && ( $this->mandatory && $value === null )) {
|
||||||
|
throw new MandatoryFieldException("Mandatory field must be provided a value.");
|
||||||
|
}
|
||||||
|
elseif ($value !== null) {
|
||||||
|
if ($this->minLength || $this->maxLength) {
|
||||||
|
if (is_string($value)) {
|
||||||
|
if (mb_strlen($value) < $this->minLength) {
|
||||||
|
throw new MinimumLengthException("Minimum {$this->minLength} string length is required.");
|
||||||
|
}
|
||||||
|
elseif (mb_strlen($value) > $this->maxLength) {
|
||||||
|
throw new MaximumLengthException("Maximum {$this->maxLength} string length has been exceeded.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->regexFormat) {
|
||||||
|
if (! preg_match($this->regexFormat, $value)) {
|
||||||
|
throw new RegexFormatException("Regex format {$this->regexFormat} is not valid for value {$value}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -13,5 +13,6 @@ class EntityField
|
|||||||
public ?string $regexFormat = null,
|
public ?string $regexFormat = null,
|
||||||
public mixed $example = null,
|
public mixed $example = null,
|
||||||
public ?string $field = null,
|
public ?string $field = null,
|
||||||
|
public ?string $setterMethod = null,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
120
src/Cli/CodeGeneration.php
Normal file
120
src/Cli/CodeGeneration.php
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Cli;
|
||||||
|
|
||||||
|
use DI\Attribute\Inject;
|
||||||
|
use Lean\Lean;
|
||||||
|
use Lean\Api\Action;
|
||||||
|
use Mcnd\CLI\{Terminal, OptionValueTypeEnum, OptionStack};
|
||||||
|
use Notes\CLI\Attribute\{Option, Command};
|
||||||
|
use Psr\Http\Message\{ServerRequestInterface, ResponseInterface};
|
||||||
|
|
||||||
|
#[Command(description: PHP_EOL . <<<CLI
|
||||||
|
#[color:gray, bg: black]╔═══════════════════════════════════════════════════════════════════╗[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓████████▓▒░░▒▓██████▓▒░░▒▓███████▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓██████▓▒░ ░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓█▓▒░ ░▒▓█▓▒░ ░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ░▒▓████████▓▒░▒▓████████▓▒░▒▓█▓▒░░▒▓█▓▒░▒▓█▓▒░░▒▓█▓▒░ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]║ A P I ║[#]
|
||||||
|
#[color:gray, bg: black]║ ║[#]
|
||||||
|
#[color:gray, bg: black]╚═══════════════════════════════════════════════════════════════════╝[#]
|
||||||
|
|
||||||
|
Try #[color:gray, bg: black]'lean api --help'[#] for more information.
|
||||||
|
CLI
|
||||||
|
, command: 'lean-api')]
|
||||||
|
#[Option(switch: ['-h', '--help'], description: "Display help for the given command. When no command is given display help for the list command")]
|
||||||
|
class CodeGeneration
|
||||||
|
{
|
||||||
|
use \Lean\ControllerTrait;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
public Lean $lean;
|
||||||
|
|
||||||
|
#[Command(description: "Create an entity and it's related boilerplate", command: "entity:generate")]
|
||||||
|
#[Option(switch: ['-p', '--application'], description: "Application to target", value: OptionValueTypeEnum::RequiredValue)]
|
||||||
|
#[Option(switch: ['-n', '--name'], description: "Write generated content into project", value: OptionValueTypeEnum::RequiredValue)]
|
||||||
|
#[Option(switch: ['-m', '--namespace'], description: "Which PSR-4 namespace should be used", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-f', '--form'], description: "Form and context name", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-s', '--saveForm'], description: "Include or not a save form", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-d', '--deleteForm'], description: "Include or not a delete form", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-a', '--api'], description: "Include or not an API controller", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-c', '--api-docs'], description: "Include or not an API documentation page", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-w', '--web'], description: "Include or not a web controller", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-f', '--factory'], description: "Factory file to add to", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
#[Option(switch: ['-w', '--write'], description: "Write generated content into project's folder path",)]
|
||||||
|
#[Option(switch: ['-o', '--overwrite'], description: "Overwrite every files by default", value: OptionValueTypeEnum::NoValue)]
|
||||||
|
#[Option(switch: ['-t', '--default-answers'], description: "Auto-accept every default answers", value: OptionValueTypeEnum::NoValue)]
|
||||||
|
##[Option(switch: ['-p', '--path'], description: "Base project save path", value: OptionValueTypeEnum::OptionalValue)]
|
||||||
|
public function generate(ServerRequestInterface $request, array $arguments, OptionStack $options, string $rest): ResponseInterface
|
||||||
|
{
|
||||||
|
$name = $options->get('name')->value() ?: readline('Entity name : ');
|
||||||
|
|
||||||
|
if ( empty($name)) {
|
||||||
|
throw new \InvalidArgumentException("An entity name must be provided");
|
||||||
|
}
|
||||||
|
|
||||||
|
$applicationList = array_reverse(array_map(fn($e) => $e->name, $this->lean->applications));
|
||||||
|
$app = $options->get('application')->value() ?? Terminal::proposeListChoice("Select an application : ", $applicationList);
|
||||||
|
|
||||||
|
$entityGenerate = new Action\Console\EntityGenerate(
|
||||||
|
application: $this->lean->getApplication($applicationList[$app]),
|
||||||
|
name: $name,
|
||||||
|
overwrite: $options->get('overwrite')->value() ?? false,
|
||||||
|
default: $options->get('default-answers')->value() ?? false,
|
||||||
|
);
|
||||||
|
|
||||||
|
if ("" === ( $entityGenerate->namespace = $options->get('namespace')->value() ?: "" )) {
|
||||||
|
$entityGenerate->readNamespace();
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityGenerate->readEntity();
|
||||||
|
|
||||||
|
if ($options->get('saveForm')->value() ?? Terminal::proposeListChoice("Add a save form ?", [ true => 'Yes', false => 'No', ])) {
|
||||||
|
$entityGenerate->readSaveForm();
|
||||||
|
$hasFactory = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options->get('deleteForm')->value() ?? Terminal::proposeListChoice("Add a delete form ?", [ true => 'Yes', false => 'No', ])) {
|
||||||
|
$entityGenerate->readDeleteForm();
|
||||||
|
$hasFactory = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($hasFactory) && (
|
||||||
|
$options->get('factory')->value() ?? Terminal::proposeListChoice("Add to form factory ?", [ true => 'Yes', false => 'No', ]))
|
||||||
|
) {
|
||||||
|
$entityGenerate->readFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options->get('web')->value() ?? Terminal::proposeListChoice("Add a Web controller ?", [ true => 'Yes', false => 'No', ])) {
|
||||||
|
$entityGenerate->readWeb();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($options->get('api')->value() ?? Terminal::proposeListChoice("Add an API controller ?", [ true => 'Yes', false => 'No', ])) {
|
||||||
|
$entityGenerate->readApi();
|
||||||
|
|
||||||
|
if ($options->get('api-docs')->value() ?? Terminal::proposeListChoice("Provides documentation for this route ?", [ true => 'Yes', false => 'No', ])) {
|
||||||
|
$entityGenerate->includeDocs = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$entityGenerate->generate();
|
||||||
|
|
||||||
|
if ($options->get('write')->value() ?? Terminal::proposeListChoice("Write to disc or output results only ?", [ true => 'Write to disc', false => 'Output results only', ])) {
|
||||||
|
$entityGenerate->writeToDisc();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$entityGenerate->outputResults();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->renderCLI($request, "done");
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/Controller/Debug.php
Normal file
23
src/Controller/Debug.php
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Controller;
|
||||||
|
|
||||||
|
use Lean\Api\{Controller, Entity, Form, Lib};
|
||||||
|
use Notes\Route\Attribute\Method\Route;
|
||||||
|
use Notes\Route\Attribute\Object\Route as RouteObj;
|
||||||
|
use Notes\Security\Attribute\Security;
|
||||||
|
use Notes\Security\Attribute\Taxus;
|
||||||
|
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
|
||||||
|
|
||||||
|
#[Security(locked: false)]
|
||||||
|
#[RouteObj(base: "/lean/debug")]
|
||||||
|
class Debug {
|
||||||
|
|
||||||
|
use \Lean\Api\Lib\ControllerTrait;
|
||||||
|
|
||||||
|
#[Route("/", name: "lean.api:debug-doc", method: "GET", description: "Documentation section")]
|
||||||
|
public function documentation(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->renderMarkdown(LEAN_API_PROJECT_PATH . "/meta/docs/lean-api.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
|
||||||
|
}
|
||||||
|
}
|
||||||
46
src/Controller/Debug/Errors.php
Normal file
46
src/Controller/Debug/Errors.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Controller\Debug;
|
||||||
|
|
||||||
|
use Lean\Api\{Controller, Entity, Form, Lib};
|
||||||
|
use Notes\Route\Attribute\Method\Route;
|
||||||
|
use Notes\Route\Attribute\Object\Route as RouteObj;
|
||||||
|
use Notes\Security\Attribute\Security;
|
||||||
|
use Notes\Security\Attribute\Taxus;
|
||||||
|
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface};
|
||||||
|
|
||||||
|
#[Security(locked: false)]
|
||||||
|
## [Taxus(Entity\UserTypeEnum::Developer)]
|
||||||
|
#[RouteObj(base: "/lean/debug/errors")]
|
||||||
|
class Errors {
|
||||||
|
|
||||||
|
use \Lean\Api\Lib\ControllerTrait;
|
||||||
|
|
||||||
|
#[Route("/documentation", name: "lean.api:errors-doc", method: "GET", description: "Documentation section")]
|
||||||
|
public function documentation(ServerRequestInterface $request, array $arguments) : ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->renderMarkdown(LEAN_API_PROJECT_PATH . "/meta/docs/debug/errors.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/", name: "lean.api:errors-list", method: [ "GET" ], description: "List every errors received")]
|
||||||
|
public function list(ServerRequestInterface $request, array $arguments): ResponseInterface
|
||||||
|
{
|
||||||
|
$search = Entity\Error::searchRequest()->fromRequest($request);
|
||||||
|
$collection = Entity\Error::repository()->filterServerRequest($search)->loadAll()->iterate(function($e) {
|
||||||
|
unset($e->trace);
|
||||||
|
});
|
||||||
|
|
||||||
|
return $this->output($collection, $search->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Route("/{id:\d+}", name: "lean.api:errors-single", method: [ "GET" ], description: "Get a single error from it's ID")]
|
||||||
|
public function single(ServerRequestInterface $request, array $arguments): ResponseInterface
|
||||||
|
{
|
||||||
|
$type = Entity\Error::class;
|
||||||
|
$request = $this->searchEntityFromRequest($request, $type);
|
||||||
|
$result = $request->getAttribute('lean.searchRequest');
|
||||||
|
|
||||||
|
return $this->output($result[$type]->entity, $result[$type]->search->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -15,6 +15,9 @@ trait DescriptorTrait
|
|||||||
elseif ($value instanceof \UnitEnum) {
|
elseif ($value instanceof \UnitEnum) {
|
||||||
return $value::class . "::" . $value->name;
|
return $value::class . "::" . $value->name;
|
||||||
}
|
}
|
||||||
|
elseif (is_array($value)) {
|
||||||
|
return sprintf('[ %s ]', implode(', ', array_map([ self::class, 'displayValue' ], $value)));
|
||||||
|
}
|
||||||
|
|
||||||
return $value;
|
return $value;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/Entity/Field/Date.php
Normal file
8
src/Entity/Field/Date.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Entity\Field;
|
||||||
|
|
||||||
|
class Date extends Datetime {
|
||||||
|
|
||||||
|
public string $format = "Y-m-d";
|
||||||
|
}
|
||||||
11
src/Entity/Field/Datetime.php
Normal file
11
src/Entity/Field/Datetime.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Entity\Field;
|
||||||
|
|
||||||
|
class Datetime extends \Ulmus\Entity\Field\Datetime
|
||||||
|
{
|
||||||
|
public function jsonSerialize(): mixed
|
||||||
|
{
|
||||||
|
return (string) $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
src/Entity/Field/Time.php
Normal file
8
src/Entity/Field/Time.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Entity\Field;
|
||||||
|
|
||||||
|
class Time extends Datetime {
|
||||||
|
|
||||||
|
public string $format = "H:i:s";
|
||||||
|
}
|
||||||
11
src/Entity/User/UserPrivilegeInterface.php
Normal file
11
src/Entity/User/UserPrivilegeInterface.php
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Entity\User;
|
||||||
|
|
||||||
|
interface UserPrivilegeInterface
|
||||||
|
{
|
||||||
|
public function isDeveloper() : bool;
|
||||||
|
public function isAdmin() : bool;
|
||||||
|
public function isUser() : bool;
|
||||||
|
public function isAnonymous() : bool;
|
||||||
|
}
|
||||||
@ -36,6 +36,13 @@ class EntityDescriptor
|
|||||||
if ($field) {
|
if ($field) {
|
||||||
$types = $property->getTypes();
|
$types = $property->getTypes();
|
||||||
|
|
||||||
|
if ($property->value ?? false) {
|
||||||
|
$default = $property->value instanceof \BackedEnum ? $property->value->value : $property->value;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$default = $field->object->attributes['default'] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
$fields[] = [
|
$fields[] = [
|
||||||
'name' => $property->name,
|
'name' => $property->name,
|
||||||
'description' => $entityField->object->description ?? "",
|
'description' => $entityField->object->description ?? "",
|
||||||
@ -45,6 +52,7 @@ class EntityDescriptor
|
|||||||
'allowNulls' => $property->allowsNull(),
|
'allowNulls' => $property->allowsNull(),
|
||||||
'length' => $field->object->length ?? null,
|
'length' => $field->object->length ?? null,
|
||||||
'readonly' => $field->object->readonly,
|
'readonly' => $field->object->readonly,
|
||||||
|
'default' => $default,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,8 +65,15 @@ class EntityDescriptor
|
|||||||
$field = $property->getAttribute(SearchParameter::class);
|
$field = $property->getAttribute(SearchParameter::class);
|
||||||
|
|
||||||
if ($field) {
|
if ($field) {
|
||||||
|
$possibleValues = null;
|
||||||
$types = $property->getTypes();
|
$types = $property->getTypes();
|
||||||
|
|
||||||
|
foreach($types as $type) {
|
||||||
|
if (! $type->builtIn && enum_exists($type->type)) {
|
||||||
|
$possibleValues = implode(', ', array_map(fn($e) => $e->value, $type->type::cases()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$searchFields[] = [
|
$searchFields[] = [
|
||||||
'name' => $property->name,
|
'name' => $property->name,
|
||||||
'description' => $field->object->description,
|
'description' => $field->object->description,
|
||||||
@ -66,6 +81,7 @@ class EntityDescriptor
|
|||||||
'tag' => $field->tag,
|
'tag' => $field->tag,
|
||||||
'type' => implode(' | ', array_map(fn($e) => $e->type, $types)),
|
'type' => implode(' | ', array_map(fn($e) => $e->type, $types)),
|
||||||
'allowNulls' => $property->allowsNull(),
|
'allowNulls' => $property->allowsNull(),
|
||||||
|
'possibleValues' => $possibleValues,
|
||||||
'default' => $this->displayValue($property->value ?? "")
|
'default' => $this->displayValue($property->value ?? "")
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/Exception/MandatoryFieldException.php
Normal file
5
src/Exception/MandatoryFieldException.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Exception;
|
||||||
|
|
||||||
|
class MandatoryFieldException extends \InvalidArgumentException {}
|
||||||
5
src/Exception/MaximumLengthException.php
Normal file
5
src/Exception/MaximumLengthException.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Exception;
|
||||||
|
|
||||||
|
class MaximumLengthException extends \InvalidArgumentException {}
|
||||||
5
src/Exception/MinimumLengthException.php
Normal file
5
src/Exception/MinimumLengthException.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Exception;
|
||||||
|
|
||||||
|
class MinimumLengthException extends \InvalidArgumentException {}
|
||||||
5
src/Exception/RegexFormatException.php
Normal file
5
src/Exception/RegexFormatException.php
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Exception;
|
||||||
|
|
||||||
|
class RegexFormatException extends \InvalidArgumentException {}
|
||||||
39
src/Factory/DebugFormFactory.php
Normal file
39
src/Factory/DebugFormFactory.php
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Factory;
|
||||||
|
|
||||||
|
use Lean\Api\{Factory, Form, Entity, Lib};
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
use Picea\Ui\Method\FormContextInterface;
|
||||||
|
use Picea\Ui\Method\FormInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
|
||||||
|
class DebugFormFactory implements DebugFormFactoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected ServerRequestInterface $request,
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $messageFactory,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function errorSave(EntityInterface|Entity\Error $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new Form\Debug\ErrorSave($this->languageHandler, $this->messageFactory, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errorSaveContext(? ServerRequestInterface $request = null, ? string $formName = null): FormContextInterface
|
||||||
|
{
|
||||||
|
return new Form\Debug\ErrorContext($request ?: $this->request, $formName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errorDelete(EntityInterface|Entity\Error $entity): FormInterface
|
||||||
|
{
|
||||||
|
return new Form\Debug\ErrorDelete($this->languageHandler, $this->messageFactory, $entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function errorDeleteContext(?ServerRequestInterface $request = null, ?string $formName = null): FormContextInterface
|
||||||
|
{
|
||||||
|
return new Lib\FormContext($request, $formName);
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/Factory/DebugFormFactoryInterface.php
Normal file
21
src/Factory/DebugFormFactoryInterface.php
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Factory;
|
||||||
|
|
||||||
|
use Picea\Ui\Method\FormContextInterface;
|
||||||
|
use Picea\Ui\Method\FormInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
use Lean\Api\Entity;
|
||||||
|
|
||||||
|
interface DebugFormFactoryInterface
|
||||||
|
{
|
||||||
|
public function __construct(ServerRequestInterface $request, LanguageHandler $languageHandler, MessageFactoryInterface $messageFactory,);
|
||||||
|
|
||||||
|
### ERROR SAVE
|
||||||
|
public function errorSave(Entity\Error|EntityInterface $entity): FormInterface;
|
||||||
|
public function errorSaveContext(?ServerRequestInterface $request = null, ? string $formName = null): FormContextInterface;
|
||||||
|
public function errorDelete(Entity\Error|EntityInterface $entity): FormInterface;
|
||||||
|
public function errorDeleteContext(?ServerRequestInterface $request = null, ? string $formName = null): FormContextInterface;
|
||||||
|
}
|
||||||
13
src/Factory/MessageFactoryInterface.php
Normal file
13
src/Factory/MessageFactoryInterface.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Factory;
|
||||||
|
|
||||||
|
interface MessageFactoryInterface
|
||||||
|
{
|
||||||
|
public static function generateSuccess(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
public static function generateError(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
public static function generateWarning(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
public static function generateInformation(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
public static function generateDebug(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
public static function generateTrace(string $message, ? string $header = null, ? string $class = null) : self;
|
||||||
|
}
|
||||||
45
src/Form/Debug/ErrorContext.php
Normal file
45
src/Form/Debug/ErrorContext.php
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Form\Debug;
|
||||||
|
|
||||||
|
use Lean\Api\{Entity};
|
||||||
|
use Lean\Api\Attribute\ContextField;
|
||||||
|
use Picea\Ui\Method\FormContext;
|
||||||
|
use Ulmus\Api\Common\MethodEnum;
|
||||||
|
use Ulmus\Entity\Field\Datetime;
|
||||||
|
|
||||||
|
#[ContextField(description: "This form saves an error received by the API")]
|
||||||
|
class ErrorContext extends FormContext
|
||||||
|
{
|
||||||
|
#[ContextField(description: "URL which the error was received from", hidden: true, )]
|
||||||
|
public string $url;
|
||||||
|
|
||||||
|
#[ContextField( description: "HTTP method used when this error was generated", hidden: true, )]
|
||||||
|
public MethodEnum|string $httpMethod;
|
||||||
|
|
||||||
|
#[ContextField(description: "Error message generated")]
|
||||||
|
public string $message;
|
||||||
|
|
||||||
|
#[ContextField(description: "HTTP error code")]
|
||||||
|
public int $code;
|
||||||
|
|
||||||
|
#[ContextField(description: "File in which the error occured")]
|
||||||
|
public string $file;
|
||||||
|
|
||||||
|
#[ContextField(description: "Line of the error")]
|
||||||
|
public int $line;
|
||||||
|
|
||||||
|
#[ContextField(description: "Complete stack trace (when available)")]
|
||||||
|
public array $trace;
|
||||||
|
|
||||||
|
public function valid(? Entity\Error $error = null): bool
|
||||||
|
{
|
||||||
|
$server = $this->getRequest()->getServerParams();
|
||||||
|
|
||||||
|
$this->url = ( ( 'on' === ( $server['HTTPS'] ?? false ) ) ? 'https' : 'http' ) . '://' . $server['HTTP_HOST'] . $server["REQUEST_URI"];
|
||||||
|
|
||||||
|
$this->httpMethod = MethodEnum::from($server['REQUEST_METHOD']);
|
||||||
|
|
||||||
|
return parent::valid();
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/Form/Debug/ErrorSave.php
Normal file
20
src/Form/Debug/ErrorSave.php
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Form\Debug;
|
||||||
|
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
use Lean\Api\{Lib, Entity};
|
||||||
|
|
||||||
|
class ErrorSave extends \Lean\Api\Form\Save {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $message,
|
||||||
|
public Entity\Error $entity,
|
||||||
|
) {
|
||||||
|
parent::__construct($this->languageHandler, $this->message);
|
||||||
|
|
||||||
|
$this->contextClass = ErrorContext::class;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,37 +6,28 @@ use CSSLSJ\ExamenFga\Api\{Lib};
|
|||||||
use Picea\Ui\Method\{FormContextInterface};
|
use Picea\Ui\Method\{FormContextInterface};
|
||||||
use Ulmus\Entity\EntityInterface;
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
|
||||||
class Delete implements \Picea\Ui\Method\FormInterface {
|
abstract class Delete extends Form implements \Picea\Ui\Method\FormInterface {
|
||||||
|
|
||||||
public function __construct(
|
|
||||||
public EntityInterface $entity,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getEntity() : EntityInterface
|
|
||||||
{
|
|
||||||
return $this->entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function initialize(FormContextInterface $context) : void {}
|
|
||||||
|
|
||||||
public function validate(FormContextInterface $context) : bool
|
public function validate(FormContextInterface $context) : bool
|
||||||
{
|
{
|
||||||
if ( ! $this->entity->isLoaded() ) {
|
if ( ! $this->getEntity()->isLoaded() ) {
|
||||||
$context->pushMessage(Lib\Message::generateError(
|
$context->pushMessage($this->message::generateError(
|
||||||
$this->lang('lean.api.form.delete.error.entity')
|
$this->lang('lean.api.form.delete.error.entity')
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
return $context->valid();
|
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(FormContextInterface $context) : void
|
public function execute(FormContextInterface $context) : mixed
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ( $this->getEntity()::repository()->destroy($this->getEntity()) ) {
|
if ( $this->getEntity()::repository()->destroy($this->getEntity()) ) {
|
||||||
$context->pushMessage(Lib\Message::generateSuccess(
|
$context->pushMessage($this->message::generateSuccess(
|
||||||
$this->lang('lean.api.form.delete.success.save')
|
$this->lang('lean.api.form.delete.success.save')
|
||||||
));
|
));
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new \InvalidArgumentException($this->lang('lean.api.form.delete.error.save'));
|
throw new \InvalidArgumentException($this->lang('lean.api.form.delete.error.save'));
|
||||||
|
|||||||
49
src/Form/Form.php
Normal file
49
src/Form/Form.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Form;
|
||||||
|
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
use Picea\Ui\Method\FormContextInterface;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
use Ulmus\EntityCollection;
|
||||||
|
|
||||||
|
abstract class Form
|
||||||
|
{
|
||||||
|
protected string $contextClass;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $message,
|
||||||
|
# public EntityInterface $entity,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function validate(FormContextInterface $context) : bool
|
||||||
|
{
|
||||||
|
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initialize(FormContextInterface $context) : void
|
||||||
|
{
|
||||||
|
if(isset($this->contextClass) && ! $context instanceof $this->contextClass ) {
|
||||||
|
throw new \LogicException(
|
||||||
|
sprintf("Your context type should be a %s", $this->contextClass)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lang(string $key, array $variables = [])
|
||||||
|
{
|
||||||
|
return $this->languageHandler->languageFromKey($key, $variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getEntity() : EntityInterface|EntityCollection
|
||||||
|
{
|
||||||
|
if (! isset($this->entity)) {
|
||||||
|
throw new \InvalidArgumentException($this->lang("lean.api.form.error.entity"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
src/Form/MessageTypeEnum.php
Normal file
13
src/Form/MessageTypeEnum.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Form;
|
||||||
|
|
||||||
|
enum MessageTypeEnum
|
||||||
|
{
|
||||||
|
case Error;
|
||||||
|
case Success;
|
||||||
|
case Information;
|
||||||
|
case Warning;
|
||||||
|
case Debug;
|
||||||
|
case Trace;
|
||||||
|
}
|
||||||
@ -2,71 +2,96 @@
|
|||||||
|
|
||||||
namespace Lean\Api\Form;
|
namespace Lean\Api\Form;
|
||||||
|
|
||||||
use CSSLSJ\ExamenFga\Api\{Form\Location\SessionContext, Lib, Entity};
|
use CSLSJ\Lean\Form\Session\EmailContext;
|
||||||
use Picea\Ui\Method\{ FormContextInterface, Message\ErrorMessage };
|
use Picea\Ui\Method\{FormContext, FormContextInterface};
|
||||||
|
|
||||||
|
use Lean\Api\Attribute\ContextField;
|
||||||
|
use Lean\Api\Exception\MandatoryFieldException;
|
||||||
|
use Notes\Common\ReflectedProperty;
|
||||||
|
use Notes\ObjectReflection;
|
||||||
use Ulmus\Attribute\Property\Field;
|
use Ulmus\Attribute\Property\Field;
|
||||||
use Lean\Api\Attribute\EntityField;
|
use Lean\Api\Attribute\EntityField;
|
||||||
use Lean\LanguageHandler;
|
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
|
||||||
|
|
||||||
use Ulmus\Entity\EntityInterface;
|
use Ulmus\Entity\EntityInterface;
|
||||||
use Ulmus\Entity\Field\Datetime;
|
use Ulmus\Entity\Field\Datetime;
|
||||||
use function CSSLSJ\ExamenFga\Api\View\{ lang };
|
|
||||||
|
|
||||||
abstract class Save implements \Picea\Ui\Method\FormInterface {
|
abstract class Save extends Form implements \Picea\Ui\Method\FormInterface {
|
||||||
|
|
||||||
public function __construct(
|
protected array $reflectedProperties;
|
||||||
protected LanguageHandler $languageHandler,
|
|
||||||
# public EntityInterface $entity,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public function getEntity() : EntityInterface
|
protected string $contextClass = FormContext::class;
|
||||||
|
|
||||||
|
public function initialize(FormContextInterface $context) : void
|
||||||
{
|
{
|
||||||
if (! isset($this->entity)) {
|
if ( ! $this->getEntity()->isLoaded() ) {
|
||||||
throw new \InvalidArgumentException($this->lang("lean.api.form.save.error.entity"));
|
if (method_exists($context, 'initializeEntity')) {
|
||||||
|
$context->initializeEntity($this->getEntity());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assignContextToEntity($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $this->entity;
|
parent::initialize($context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validate(FormContextInterface $context) : bool
|
public function validate(FormContextInterface $context) : bool
|
||||||
{
|
{
|
||||||
# Context is validating inputs
|
# Context validates ContextField attributes containing properties on empty entity
|
||||||
|
$this->validateContextFields($context);
|
||||||
|
|
||||||
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function execute(FormContextInterface $context) : void
|
public function execute(FormContextInterface $context) : mixed
|
||||||
{
|
{
|
||||||
$entity = $this->getEntity();
|
$entity = $this->getEntity();
|
||||||
|
|
||||||
if ($entity->isLoaded() ) {
|
if ($entity->isLoaded()) {
|
||||||
$entity->updatedAt = new Datetime();
|
if (property_exists($entity, 'updatedAt') && $entity->repository()->generateDatasetDiff($entity) ) {
|
||||||
|
$cls = $entity::resolveEntity()->field('updatedAt')->type->type;
|
||||||
|
$entity->updatedAt = new $cls();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$entity->createdAt = new Datetime();
|
if (property_exists($entity, 'createdAt') && empty($entity->createdAt)) {
|
||||||
|
$cls = $entity::resolveEntity()->field('createdAt')->type->type;
|
||||||
|
$entity->createdAt = new $cls();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$this->assignContextToEntity($context);
|
$this->assignContextToEntity($context);
|
||||||
|
|
||||||
if ( $entity::repository()->save($entity) ) {
|
if ( $saved = $entity::repository()->save($entity) ) {
|
||||||
$context->pushMessage(Lib\Message::generateSuccess(
|
$context->pushSuccessMessage('lean.api.form.save.success.save');
|
||||||
$this->lang('lean.api.form.save.success.entity')
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
throw new \InvalidArgumentException($this->lang('lean.api.form.save.error.entity'));
|
$context->pushWarningMessage('lean.api.form.save.warning.entity', [ 'entity' => substr($this->entity::class, strrpos($this->entity::class, '\\') + 1) ]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch(\Throwable $ex) {
|
catch(\PDOException $ex) {
|
||||||
throw new \ErrorException($this->lang('lean.api.form.save.error.pdo', [ 'error' => $ex->getMessage() ]));
|
throw new \PDOException($this->lang('lean.api.form.save.error.pdo', [ 'error' => $ex->getMessage() ]));
|
||||||
}
|
}
|
||||||
|
catch(\Throwable $ex) {
|
||||||
|
$context->pushErrorMessage($ex->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $saved ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function lang(string $key, array $variables = [])
|
protected function validateContextFields(FormContextInterface $context) : void
|
||||||
{
|
{
|
||||||
return $this->languageHandler->languageFromKey($key, $variables);
|
foreach ($this->reflectedProperties($context) as $name => $property) {
|
||||||
|
$attribute = $property->getAttribute(ContextField::class);
|
||||||
|
|
||||||
|
if ($attribute) {
|
||||||
|
try {
|
||||||
|
$attribute->object->assertValueSpecs($context->$name ?? null, ! $this->getEntity()->isLoaded());
|
||||||
|
} catch (MandatoryFieldException $e) {
|
||||||
|
throw new MandatoryFieldException("An error occured with field '$name': " . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function assignContextToEntity(FormContextInterface $context) : void
|
protected function assignContextToEntity(FormContextInterface $context) : void
|
||||||
@ -76,22 +101,47 @@ abstract class Save implements \Picea\Ui\Method\FormInterface {
|
|||||||
foreach($entity::resolveEntity()->fieldList() as $key => $property) {
|
foreach($entity::resolveEntity()->fieldList() as $key => $property) {
|
||||||
$field = $property->getAttribute(Field::class)->object;
|
$field = $property->getAttribute(Field::class)->object;
|
||||||
|
|
||||||
if (! $field->readonly) {
|
if (! $field->readonly || ! $entity->isLoaded()) {
|
||||||
|
|
||||||
$apiField = $property->getAttribute(EntityField::class)->object ?? null;
|
$apiField = $property->getAttribute(EntityField::class)->object ?? null;
|
||||||
|
|
||||||
if ($apiField) {
|
if ($apiField) {
|
||||||
if ($apiField->field) {
|
$var = $apiField->field ?: $key;
|
||||||
if ( isset($context->{$apiField->field}) ) {
|
|
||||||
$entity->$key = $context->{$apiField->field};
|
if ( property_exists($context, $var) && ( new \ReflectionProperty($context, $var) )->isInitialized($context) ) {
|
||||||
|
if ($apiField->setterMethod) {
|
||||||
|
# Use a setter method
|
||||||
|
call_user_func([ $entity, $apiField->setterMethod ], $context->{$var});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Direct property set
|
||||||
|
$entity->$key = $context->{$var};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
elseif (! $entity->isLoaded()) {
|
||||||
if ( isset($context->{$key}) ) {
|
# Applies 'default' value assigned to ContextField property
|
||||||
$entity->$key = $context->{$key};
|
$attr = $this->reflectedProperties($context)[$var] ?? null;
|
||||||
|
|
||||||
|
if ($attr) {
|
||||||
|
$contextField = $attr->getAttribute(ContextField::class);
|
||||||
|
|
||||||
|
if ($contextField) {
|
||||||
|
if (isset($contextField->object->default)) {
|
||||||
|
|
||||||
|
$entity->$key = $contextField->object->default;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function reflectedProperties(FormContextInterface $context) : array
|
||||||
|
{
|
||||||
|
$this->reflectedProperties ??= ObjectReflection::fromClass($context::class)->reflectProperties(\ReflectionProperty::IS_PUBLIC);
|
||||||
|
|
||||||
|
return $this->reflectedProperties;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ class FormDescriptor
|
|||||||
|
|
||||||
foreach($forms as $form) {
|
foreach($forms as $form) {
|
||||||
$fields = [];
|
$fields = [];
|
||||||
$propertyHtml = "";
|
|
||||||
$formName = is_object($form) ? $form::class : $form;
|
$formName = is_object($form) ? $form::class : $form;
|
||||||
$reflector = new ObjectReflection($formName);
|
$reflector = new ObjectReflection($formName);
|
||||||
|
|
||||||
@ -24,17 +23,21 @@ class FormDescriptor
|
|||||||
foreach($reflector->reflectProperties() as $property) {
|
foreach($reflector->reflectProperties() as $property) {
|
||||||
$field = $property->getAttribute(ContextField::class);
|
$field = $property->getAttribute(ContextField::class);
|
||||||
|
|
||||||
if ($field) {
|
if ($field && ! $field->object->hidden ) {
|
||||||
$types = $property->getTypes();
|
$types = $property->getTypes();
|
||||||
|
|
||||||
$fields[] = [
|
$fields[] = [
|
||||||
'name' => $property->name,
|
'name' => $property->name,
|
||||||
|
'mandatory' => $field->object->mandatory,
|
||||||
'description' => $field->object->description,
|
'description' => $field->object->description,
|
||||||
'type' => $field->object->type ?? implode(' | ', array_map(fn($e) => $e->type, $types)),
|
'type' => $field->object->type ?? $types,
|
||||||
'allowNulls' => $property->allowsNull(),
|
'allowNulls' => $property->allowsNull(),
|
||||||
'regexPattern' =>$field->object->regexFormat ?? "aucun",
|
'regexPattern' => $field->object->regexFormat ?? null,
|
||||||
'minLength' =>$field->object->minLength ?? "aucune",
|
'minLength' => $field->object->minLength ?? null,
|
||||||
'maxLength' =>$field->object->maxLength ?? "aucune",
|
'maxLength' => $field->object->maxLength ?? null,
|
||||||
'example' =>$field->object->example,
|
'example' => $field->object->example ?? null,
|
||||||
|
'default' => isset($field->object->default) ? json_encode($field->object->default) : null,
|
||||||
|
'values' => $this->generateExampleValues($types),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,4 +51,17 @@ class FormDescriptor
|
|||||||
|
|
||||||
return $list;
|
return $list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function generateExampleValues(array $types) : string|array
|
||||||
|
{
|
||||||
|
$values = [];
|
||||||
|
|
||||||
|
foreach($types as $type) {
|
||||||
|
if ( enum_exists($type->type) ) {
|
||||||
|
$values = array_merge($values, array_map(fn($e) => $e->value, $type->type::cases()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $values;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,14 +2,24 @@
|
|||||||
|
|
||||||
namespace Lean\Api;
|
namespace Lean\Api;
|
||||||
|
|
||||||
|
use DI\Attribute\Inject;
|
||||||
use League\CommonMark\CommonMarkConverter;
|
use League\CommonMark\CommonMarkConverter;
|
||||||
|
use Lean\Api\Lib\ApiSearchRequestInterface;
|
||||||
use Notes\Attribute\Ignore;
|
use Notes\Attribute\Ignore;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
use Ulmus\EntityCollection;
|
||||||
|
use Ulmus\SearchRequest\SearchRequestInterface;
|
||||||
|
|
||||||
trait LeanApiTrait
|
trait LeanApiTrait
|
||||||
{
|
{
|
||||||
use DescriptorTrait;
|
use DescriptorTrait;
|
||||||
|
|
||||||
|
#[Inject]
|
||||||
|
protected ContainerInterface $container;
|
||||||
|
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
public function renderMarkdown(string $filepath, array $entities = [], array $forms = [], int $code = 200, array $headers = []) : ResponseInterface
|
public function renderMarkdown(string $filepath, array $entities = [], array $forms = [], int $code = 200, array $headers = []) : ResponseInterface
|
||||||
{
|
{
|
||||||
@ -22,10 +32,15 @@ trait LeanApiTrait
|
|||||||
if (str_contains($markdown, '{route:descriptor}'))
|
if (str_contains($markdown, '{route:descriptor}'))
|
||||||
{
|
{
|
||||||
$markdown = str_replace('{route:descriptor}', $this->renderRawView('lean-api/route_descriptor', [
|
$markdown = str_replace('{route:descriptor}', $this->renderRawView('lean-api/route_descriptor', [
|
||||||
'routes' => (new RouteDescriptor($this, $this->picea->compiler->getExtensionFromToken('url')))->getRoutes()
|
'routes' => (new RouteDescriptor($this, $this->picea->compiler->getExtensionFromToken('url'), $this->container))->getRoutes()
|
||||||
]), $markdown);
|
]), $markdown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (str_contains($markdown, '{request:debugger}'))
|
||||||
|
{
|
||||||
|
$markdown = str_replace('{request:debugger}', $this->renderRawView('lean-api/request_debugger', []), $markdown);
|
||||||
|
}
|
||||||
|
|
||||||
if (str_contains($markdown, '{entity:descriptor}'))
|
if (str_contains($markdown, '{entity:descriptor}'))
|
||||||
{
|
{
|
||||||
$markdown = str_replace('{entity:descriptor}', $this->renderRawView('lean-api/entity_descriptor', [
|
$markdown = str_replace('{entity:descriptor}', $this->renderRawView('lean-api/entity_descriptor', [
|
||||||
@ -44,10 +59,108 @@ trait LeanApiTrait
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Ignore]
|
#[Ignore]
|
||||||
protected function output(\JsonSerializable|array $data, int $count) {
|
protected function output(\JsonSerializable|array $data, int $count) : ResponseInterface
|
||||||
|
{
|
||||||
return $this->renderJson([
|
return $this->renderJson([
|
||||||
'data' => $data,
|
'data' => $data,
|
||||||
'count' => $count,
|
'count' => $count,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
|
protected function outputSearchResult(ApiSearchRequestInterface $apiSearchRequest) : ResponseInterface
|
||||||
|
{
|
||||||
|
return $this->output($apiSearchRequest->getResult(), $apiSearchRequest->getSearch()->count);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
|
protected function searchEntityFromRequest(ServerRequestInterface $request, string $entityType, ? string $resultKey = null) : ServerRequestInterface
|
||||||
|
{
|
||||||
|
$search = $entityType::searchRequest()->fromRequest($request);
|
||||||
|
$entity = $entityType::repository()->filterServerRequest($search)->loadOne() ?? false;
|
||||||
|
|
||||||
|
if (! $entity ) {
|
||||||
|
throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes(), \JSON_PARTIAL_OUTPUT_ON_ERROR)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->getAttribute("lean.searchRequest") === null) {
|
||||||
|
$request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface {
|
||||||
|
public function __construct(
|
||||||
|
public readonly SearchRequestInterface $search,
|
||||||
|
public readonly EntityInterface $entity,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSearch(): SearchRequestInterface
|
||||||
|
{
|
||||||
|
return $this->search;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult(): EntityCollection|EntityInterface
|
||||||
|
{
|
||||||
|
return $this->entity;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
|
protected function searchEntitiesFromRequest(ServerRequestInterface $request, string $entityType, ? string $resultKey = null) : ServerRequestInterface
|
||||||
|
{
|
||||||
|
$search = $entityType::searchRequest()->fromRequest($request);
|
||||||
|
$entity = $entityType::repository()->filterServerRequest($search)->loadAll();
|
||||||
|
|
||||||
|
if (! $entity ) {
|
||||||
|
throw new \InvalidArgumentException(sprintf("L'entré pour l'entité demandé (%s) est introuvable avec le ou les arguments fournis '%s'", $entityType, json_encode($request->getAttributes())));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($request->getAttribute("lean.searchRequest") === null) {
|
||||||
|
$request = $request->withAttribute("lean.searchRequest", $this->searchEntityResult());
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->getAttribute("lean.searchRequest")[$resultKey ?: $entityType] = new class($search, $entity) implements ApiSearchRequestInterface {
|
||||||
|
public function __construct(
|
||||||
|
public readonly SearchRequestInterface $search,
|
||||||
|
public readonly EntityCollection $collection,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function getSearch(): SearchRequestInterface
|
||||||
|
{
|
||||||
|
return $this->search;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult(): EntityCollection|EntityInterface
|
||||||
|
{
|
||||||
|
return $this->collection;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return $request;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[Ignore]
|
||||||
|
protected function searchEntityResult() :\ArrayObject {
|
||||||
|
return new class()extends \ArrayObject implements ApiSearchRequestInterface {
|
||||||
|
|
||||||
|
public function getSearch(): SearchRequestInterface
|
||||||
|
{
|
||||||
|
if ($this->count() !== 1) {
|
||||||
|
throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this[array_key_last($this->getArrayCopy())]->getSearch();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getResult() : EntityInterface|EntityCollection {
|
||||||
|
if ($this->count() !== 1) {
|
||||||
|
throw new \LogicException("Search results contains more than one results. Impossible to continue without precising which one you needs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this[array_key_last($this->getArrayCopy())]->getResult();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
13
src/Lib/ApiSearchRequestInterface.php
Normal file
13
src/Lib/ApiSearchRequestInterface.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Lib;
|
||||||
|
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
use Ulmus\EntityCollection;
|
||||||
|
use Ulmus\SearchRequest\SearchRequestInterface;
|
||||||
|
|
||||||
|
interface ApiSearchRequestInterface
|
||||||
|
{
|
||||||
|
public function getSearch() : SearchRequestInterface;
|
||||||
|
public function getResult() : EntityCollection|EntityInterface;
|
||||||
|
}
|
||||||
13
src/Lib/ControllerTrait.php
Normal file
13
src/Lib/ControllerTrait.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Lib;
|
||||||
|
|
||||||
|
use Notes\Route\Attribute\Object\Route;
|
||||||
|
|
||||||
|
#[Route(method: ['GET', 'POST', ])]
|
||||||
|
trait ControllerTrait
|
||||||
|
{
|
||||||
|
use \Lean\ControllerTrait, \Lean\Api\LeanApiTrait {
|
||||||
|
\Lean\Api\LeanApiTrait::renderMarkdown insteadof \Lean\ControllerTrait;
|
||||||
|
}
|
||||||
|
}
|
||||||
52
src/Lib/FormContext.php
Normal file
52
src/Lib/FormContext.php
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Lib;
|
||||||
|
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Lean\LanguageHandler;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Ulmus\Entity\EntityInterface;
|
||||||
|
|
||||||
|
class FormContext extends \Picea\Ui\Method\FormContext {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected LanguageHandler $languageHandler,
|
||||||
|
protected MessageFactoryInterface $message,
|
||||||
|
ServerRequestInterface $request,
|
||||||
|
? string $formName = null
|
||||||
|
)
|
||||||
|
{
|
||||||
|
parent::__construct($request, $formName);
|
||||||
|
}
|
||||||
|
|
||||||
|
# public function initializeEntity(EntityInterface $entity) : void {}
|
||||||
|
|
||||||
|
public function lang(string $key, array $variables = [])
|
||||||
|
{
|
||||||
|
return $this->languageHandler->languageFromKey($key, $variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushSuccessMessage($key, $variables = []) {
|
||||||
|
$this->pushMessage($this->message::generateSuccess(
|
||||||
|
$this->lang($key, $variables)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushWarningMessage($key, $variables = []) {
|
||||||
|
$this->pushMessage($this->message::generateWarning(
|
||||||
|
$this->lang($key, $variables)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushErrorMessage($key, $variables = []) {
|
||||||
|
$this->pushMessage($this->message::generateError(
|
||||||
|
$this->lang($key, $variables)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pushInfoMessage($key, $variables = []) {
|
||||||
|
$this->pushMessage($this->message::generateInfo(
|
||||||
|
$this->lang($key, $variables)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
132
src/Lib/Message.php
Normal file
132
src/Lib/Message.php
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Lean\Api\Lib;
|
||||||
|
|
||||||
|
use Lean\Api\Factory\MessageFactoryInterface;
|
||||||
|
use Picea\Ui\Method\FormMessage;
|
||||||
|
|
||||||
|
class Message implements FormMessage, MessageFactoryInterface {
|
||||||
|
|
||||||
|
const MESSAGE_TYPE = [
|
||||||
|
'success' => [
|
||||||
|
'class' => 'success',
|
||||||
|
'header' => 'Succès !',
|
||||||
|
'message' => ''
|
||||||
|
],
|
||||||
|
|
||||||
|
'error' => [
|
||||||
|
'class' => 'danger',
|
||||||
|
'header' => 'Error !',
|
||||||
|
'message' => ''
|
||||||
|
],
|
||||||
|
|
||||||
|
'warning' => [
|
||||||
|
'class' => 'warning',
|
||||||
|
'header' => 'Attention !',
|
||||||
|
'message' => ''
|
||||||
|
],
|
||||||
|
|
||||||
|
'information' => [
|
||||||
|
'class' => 'information',
|
||||||
|
'header' => 'À savoir !',
|
||||||
|
'message' => ''
|
||||||
|
],
|
||||||
|
|
||||||
|
'debug' => [
|
||||||
|
'class' => 'debug',
|
||||||
|
'header' => 'Debug :',
|
||||||
|
'message' => ''
|
||||||
|
],
|
||||||
|
|
||||||
|
'trace' => [
|
||||||
|
'class' => 'trace',
|
||||||
|
'header' => 'Trace :',
|
||||||
|
'message' => ''
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
public string $type;
|
||||||
|
|
||||||
|
public string $message;
|
||||||
|
|
||||||
|
public string $class;
|
||||||
|
|
||||||
|
public ? string $header = null;
|
||||||
|
|
||||||
|
public function __construct(string $message = "", string $type = "error", ? string $header = null, ? string $class = null)
|
||||||
|
{
|
||||||
|
$this->message = $message;
|
||||||
|
|
||||||
|
$this->type = $type;
|
||||||
|
|
||||||
|
if ( $header !== null ) {
|
||||||
|
$this->header = $header;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->header = static::MESSAGE_TYPE[$type]['header'];
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $class !== null ) {
|
||||||
|
$this->class = $class;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$this->class = static::MESSAGE_TYPE[$type]['class'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function render() : string
|
||||||
|
{
|
||||||
|
return <<<HTML
|
||||||
|
<article class="message is-{$this->class}" role="alert">
|
||||||
|
<div class="message-body">
|
||||||
|
<strong>{$this->header}</strong>
|
||||||
|
<span>{$this->message}</span>
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
HTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function renderJson() : array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'name' => 'message',
|
||||||
|
'type' => $this->type,
|
||||||
|
'message' => $this->message,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isError() : bool
|
||||||
|
{
|
||||||
|
return $this->type === "error";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateSuccess(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'success', $header, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateError(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'error', $header, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateWarning(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'warning', $header, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateInformation(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'information', $header, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateDebug(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'debug', $header, $class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateTrace(string $message, ? string $header = null, ? string $class = null) : self
|
||||||
|
{
|
||||||
|
return new static($message, 'trace', $header, $class);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,29 +4,44 @@ namespace Lean\Api\Middleware;
|
|||||||
|
|
||||||
use Laminas\Diactoros\Response\JsonResponse;
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
use Lean\Factory\HttpFactory;
|
use Lean\Factory\HttpFactory;
|
||||||
|
use Picea\Ui\Method\FormHandler;
|
||||||
use Psr\Http\Message\ResponseInterface;
|
use Psr\Http\Message\ResponseInterface;
|
||||||
use Psr\Http\Message\ServerRequestInterface;
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
use Psr\Http\Server\MiddlewareInterface;
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
use Psr\Http\Server\RequestHandlerInterface;
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Lean\Api\{Factory\DebugFormFactoryInterface, Form, Entity};
|
||||||
|
|
||||||
class ApiRenderer implements MiddlewareInterface {
|
class ApiRenderer implements MiddlewareInterface {
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
protected DebugFormFactoryInterface $debugFormFactory,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$response = $handler->handle($request);
|
$response = $handler->handle($request);
|
||||||
}
|
}
|
||||||
catch(\Exception $ex) {
|
catch(\Throwable $ex) {
|
||||||
if ( ! getenv('DEBUG') ) {
|
|
||||||
|
|
||||||
|
if (static::awaitingJson($request)) {
|
||||||
return HttpFactory::createJsonResponse([
|
return HttpFactory::createJsonResponse([
|
||||||
'status' => 'failed',
|
'status' => 'failed',
|
||||||
'message' => $ex->getMessage(),
|
|
||||||
'ts' => time(),
|
'ts' => time(),
|
||||||
], 400);
|
'data' => [
|
||||||
}
|
# 'error_id' => $errorId,
|
||||||
else {
|
'message' => $ex->getMessage(),
|
||||||
throw $ex;
|
] + (getenv('DEBUG') ? [
|
||||||
|
'file' => $ex->getFile(),
|
||||||
|
'line' => $ex->getLine(),
|
||||||
|
'code' => $ex->getCode(),
|
||||||
|
'backtrace' => $ex->getTrace(), ] : [])
|
||||||
|
], 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw $ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($response instanceof JsonResponse) {
|
if ($response instanceof JsonResponse) {
|
||||||
@ -43,4 +58,30 @@ class ApiRenderer implements MiddlewareInterface {
|
|||||||
|
|
||||||
return $response;
|
return $response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function saveError(ServerRequestInterface $request, \Throwable $exception) : Entity\Error
|
||||||
|
{
|
||||||
|
$request = $request->withMethod('PUT')->withParsedBody([]);
|
||||||
|
|
||||||
|
$context = $this->debugFormFactory->errorSaveContext($request);
|
||||||
|
|
||||||
|
$context->sets([
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'code' => $exception->getCode(),
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'trace' => $exception->getTrace(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$form = $this->debugFormFactory->errorSave($entity = new Entity\Error());
|
||||||
|
|
||||||
|
new FormHandler($request, $form, $context);
|
||||||
|
|
||||||
|
return $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function awaitingJson(ServerRequestInterface $request) : bool
|
||||||
|
{
|
||||||
|
return str_contains(strtolower($request->getHeaderLine('content-type')), 'json');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
26
src/Middleware/CORS.php
Normal file
26
src/Middleware/CORS.php
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lean\Api\Middleware;
|
||||||
|
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use Lean\Factory\HttpFactory;
|
||||||
|
use Picea\Ui\Method\FormHandler;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\MiddlewareInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Lean\Api\{Factory\DebugFormFactoryInterface, Form, Entity};
|
||||||
|
|
||||||
|
class CORS implements MiddlewareInterface {
|
||||||
|
|
||||||
|
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||||
|
{
|
||||||
|
|
||||||
|
var_dump($request->getMethod() );
|
||||||
|
if ($request->getMethod() === "option") {
|
||||||
|
die("an option :)");
|
||||||
|
}
|
||||||
|
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -5,14 +5,27 @@ namespace Lean\Api;
|
|||||||
use Lean\Factory\HttpFactory;
|
use Lean\Factory\HttpFactory;
|
||||||
use Notes\ObjectReflection;
|
use Notes\ObjectReflection;
|
||||||
use Notes\Route\Attribute\Method\Route;
|
use Notes\Route\Attribute\Method\Route;
|
||||||
|
use Notes\Security\SecurityHandler;
|
||||||
use Picea\Extension\UrlExtension;
|
use Picea\Extension\UrlExtension;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use Taxus\Taxus;
|
||||||
|
|
||||||
class RouteDescriptor
|
class RouteDescriptor
|
||||||
{
|
{
|
||||||
|
protected SecurityHandler $securityHandler;
|
||||||
|
|
||||||
|
protected Taxus $taxus;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public object $controller,
|
public object $controller,
|
||||||
protected UrlExtension $urlExtension,
|
protected UrlExtension $urlExtension,
|
||||||
) {}
|
ContainerInterface $container
|
||||||
|
) {
|
||||||
|
if ($container->has(SecurityHandler::class) && $container->has(Taxus::class)) {
|
||||||
|
$this->securityHandler = $container->get(SecurityHandler::class);
|
||||||
|
$this->taxus = $container->get(Taxus::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getRoutes() : array
|
public function getRoutes() : array
|
||||||
{
|
{
|
||||||
@ -37,7 +50,9 @@ class RouteDescriptor
|
|||||||
'path' => $base.$path,
|
'path' => $base.$path,
|
||||||
'cleaned' => $cleaned,
|
'cleaned' => $cleaned,
|
||||||
'description'=> $route->description,
|
'description'=> $route->description,
|
||||||
'methods' => $route->method,
|
#'methods' =>implode(', ', (array)$route->method),
|
||||||
|
'methods' => (array) $route->method,
|
||||||
|
'privileges' => $this->getPrivilegeFromRoute($method->name),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -57,4 +72,32 @@ class RouteDescriptor
|
|||||||
|
|
||||||
return implode('/', $paths);
|
return implode('/', $paths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $method Which method are we inspecting
|
||||||
|
* @return false|array|null Returns NULL when securityHandler is undefined, FALSE when no security applies for this method and else an array of ['admin' => [ 'status' => true, 'description' => "' ], ...].
|
||||||
|
*/
|
||||||
|
protected function getPrivilegeFromRoute(string $method) : null|false|array
|
||||||
|
{
|
||||||
|
if ( isset($this->securityHandler) ){
|
||||||
|
$list = [];
|
||||||
|
|
||||||
|
if ( $this->securityHandler->isLocked($this->controller::class, $method) ) {
|
||||||
|
foreach($this->taxus->list as $name => $definition) {
|
||||||
|
if ($definition[0]->testableArguments !== null) {
|
||||||
|
if ( $this->securityHandler->hasGrantPermission($this->controller::class, $method, ...$definition[0]->testableArguments) ) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $list;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
1
view/lean-api/entity-generate/create.phtml
Normal file
1
view/lean-api/entity-generate/create.phtml
Normal file
@ -0,0 +1 @@
|
|||||||
|
@todo!
|
||||||
1
view/lean-api/entity-generate/edit.phtml
Normal file
1
view/lean-api/entity-generate/edit.phtml
Normal file
@ -0,0 +1 @@
|
|||||||
|
@todo!
|
||||||
1
view/lean-api/entity-generate/index.phtml
Normal file
1
view/lean-api/entity-generate/index.phtml
Normal file
@ -0,0 +1 @@
|
|||||||
|
@todo!
|
||||||
@ -1,65 +1,90 @@
|
|||||||
|
{% language.set "lean.api.descriptor.entity" %}
|
||||||
|
|
||||||
{% function yesOrNo(bool $toggle) : void %}
|
{% function yesOrNo(bool $toggle) : void %}
|
||||||
<span style="color: {{ $toggle ? 'green' : '#ac1b1b' }}">{{ $toggle ? 'oui' : 'non' }}</span>
|
<span style="color: {{ $toggle ? 'green' : '#ac1b1b' }}">{{ $toggle ? 'oui' : 'non' }}</span>
|
||||||
{% endfunction %}
|
{% endfunction %}
|
||||||
|
|
||||||
{% function length(int|null $length): void %}
|
{% function length(int|null|string $length): void %}
|
||||||
<span style="color:{{ $length ? 'black' : 'gray' }}">{{ $length ?? "non-défini" }}</span>
|
<span style="color:{{ $length ? 'black' : 'gray' }}">{{ $length ?? "non-défini" }}</span>
|
||||||
{% endfunction %}
|
{% endfunction %}
|
||||||
|
|
||||||
<div class="entities">
|
<div class="entities">
|
||||||
{% foreach $entities as $name => $entity %}
|
{% foreach $entities as $name => $entity %}
|
||||||
<div class="entity-wrapper" style="padding-left: 15px;border-left: 3px solid #eaa1af;">
|
<div id="{{ $entity['className'] }}" class="entity-wrapper" style="padding-left: 15px;border-left: 3px solid #eaa1af;">
|
||||||
<h4 class='entity-name'>{{ $name }}</h4>
|
<h4 class='entity-name' title="{{ $name }}">{{ $entity['className'] }}</h4>
|
||||||
<div class='description'>{{ $entity['description'] }}</div>
|
<div class='description'>{{ $entity['description'] }}</div>
|
||||||
|
|
||||||
<hr style="margin-top:15px">
|
<hr style="margin-top:15px">
|
||||||
|
|
||||||
<h5>Champs</h5>
|
<div class="fields-wrapper">
|
||||||
<ol class='fields'>
|
<div class="header-fields">Champs</div>
|
||||||
{% foreach $entity['fields'] as $field %}
|
<ol class='fields'>
|
||||||
<li class="odd-even">
|
{% foreach $entity['fields'] as $field %}
|
||||||
<div class="title">
|
<li class="odd-even">
|
||||||
<div><strong style="font-family:monospace">${{ $field['name'] }}</strong> <span style='margin-left:15px'>{{ $field['description'] }}</span></div>
|
<div class="title">
|
||||||
</div>
|
<div>
|
||||||
|
<strong style="font-family:monospace">${{ $field['name'] }} {% if ! $field['allowNulls'] %}<span style="color:red">*</span>{% endif %}</strong>
|
||||||
|
<span style='margin-left:15px'>{{ $field['description'] }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
||||||
<div style="padding:5px"><u>Champ SQL</u> : {{ $field['fieldName'] }}</div>
|
<div class="fieldname"><u>Champ SQL</u> : {{ $field['fieldName'] }}</div>
|
||||||
<div style="padding:5px"><u>Attribut</u> : {{ $field['tag'] }}</div>
|
<div class="tag"><u>Attribut</u> : {{ $field['tag'] }}</div>
|
||||||
<div style="padding:5px"><u>Type(s)</u> : {{ $field['type'] }}</div>
|
<div class="type"><u>Type(s)</u> : {{ $field['type'] }}</div>
|
||||||
<div style="padding:5px"><u>Nullable</u> {{ yesOrNo($field['allowNulls']) }}</div>
|
<div class="nullable"><u>Nullable</u> {{ yesOrNo($field['allowNulls']) }}</div>
|
||||||
<div style="padding:5px"><u>Taille</u> : {{ length($field['length']) }}</div>
|
{% if $field['default'] %}
|
||||||
<div style="padding:5px"><u>Lecture seule</u> : {{ yesOrNo($field['readonly']) }}</div>
|
<div class="default"><u>Valeur par défaut</u> : {{ $field['default'] }}</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</li>
|
<div class="length"><u>Taille</u> : {{ length($field['length']) }}</div>
|
||||||
{% endforeach %}
|
<div class="readonly"><u>Lecture seule</u> : {{ yesOrNo($field['readonly']) }}</div>
|
||||||
</ol>
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endforeach %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="search-request-wrapper">
|
{% if $entity['searchRequestFields'] %}
|
||||||
<h4 class='search-request-name'>{{ $name }}::searchRequest()</h4>
|
<div class="search-request-wrapper">
|
||||||
|
<h4 class='search-request-name' title="{{ $name }}">{{ $entity['className'] }}::searchRequest()</h4>
|
||||||
|
|
||||||
<h5>Requêtes</h5>
|
{% if $entity['searchRequestDescription'] %}
|
||||||
<div class='description' style="margin-bottom:10px">{{ $entity['searchRequestDescription'] }}</div>
|
<div class='description' style="margin-bottom:10px">{{ $entity['searchRequestDescription'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<ol class='requests'>
|
|
||||||
{% foreach $entity['searchRequestFields'] as $field %}
|
|
||||||
<li class="odd-even">
|
|
||||||
<div class="title">
|
|
||||||
<div><strong style="font-family:monospace">{{ $field['name'] }}</strong> <span style='margin-left:15px'>{{ $field['description'] }}</span></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
<div class="fields-wrapper">
|
||||||
<div><u>Paramètre de requête (GET)</u> : {{ $field['parameter'] }}</div>
|
<div class="header-fields">Champs</div>
|
||||||
<div><u>Attribut</u> : {{ $field['tag'] }}</div>
|
|
||||||
<div><u>Type(s)</u> : {{ $field['type'] }}</div>
|
<ol class='requests'>
|
||||||
<div><u>Nullable</u> : {{ yesOrNo($field['allowNulls']) }}</div>
|
{% foreach $entity['searchRequestFields'] as $field %}
|
||||||
{% if $field['default'] !== "" %}
|
<li class="odd-even">
|
||||||
<div><u>Valeur par défault</u> : {{ $this->displayValue($field['default']) }}</div>
|
<div class="title">
|
||||||
{% endif %}
|
<div>
|
||||||
</div>
|
<strong style="font-family:monospace">{{ $field['name'] }}</strong>
|
||||||
</li>
|
<span style='margin-left:15px'>{{ $field['description'] }}</span>
|
||||||
{% endforeach %}
|
</div>
|
||||||
</ol>
|
</div>
|
||||||
</div>
|
|
||||||
|
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
||||||
|
<div class="parameter"><u>Paramètre de requête (GET)</u> : {{ $field['parameter'] }}</div>
|
||||||
|
<div class="tag"><u>Attribut</u> : {{ $field['tag'] }}</div>
|
||||||
|
<div class="type"><u>Type(s)</u> : {{ $field['type'] }}</div>
|
||||||
|
{% if $field['possibleValues'] ?? false %}
|
||||||
|
<div class="possible-values"><u>Valeurs possibles</u> : {{ $field['possibleValues'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if $field['default'] !== "" %}
|
||||||
|
<div class="default"><u>Valeur par défault</u> : {{ $this->displayValue($field['default']) }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endforeach %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% or %}
|
||||||
|
<i style="color:#585858; padding:0 12px">{% _ "none" %}</i>
|
||||||
{% endforeach %}
|
{% endforeach %}
|
||||||
</div>
|
</div>
|
||||||
@ -1,38 +1,61 @@
|
|||||||
|
{% language.set "lean.api.descriptor.form" %}
|
||||||
|
|
||||||
{% function yesOrNo(bool $toggle) : void %}
|
{% function yesOrNo(bool $toggle) : void %}
|
||||||
<span style="color: {{ $toggle ? 'green' : '#ac1b1b' }}">{{ $toggle ? 'oui' : 'non' }}</span>
|
<span style="color: {{ $toggle ? 'green' : '#ac1b1b' }}">{{ $toggle ? 'oui' : 'non' }}</span>
|
||||||
{% endfunction %}
|
{% endfunction %}
|
||||||
|
|
||||||
<div class="forms">
|
<div class="forms">
|
||||||
{% foreach $forms as $name => $form %}
|
{% foreach $forms as $name => $form %}
|
||||||
<div class="single-form" style="padding-left:15px;border-left: 3px solid #9ccce6;">
|
<div class="single-form" style="padding-left:15px;border-left: 3px solid #9ccce6;" data-form="{% json.html $form %}">
|
||||||
<h4 class='form-name'>{{ $name }}</h4>
|
<h4 class='form-name'>
|
||||||
|
<span title="{{ $name }}">{{ $form['className'] }}</span>
|
||||||
|
</h4>
|
||||||
<div class='description'>{{ $form['description'] }}</div>
|
<div class='description'>{{ $form['description'] }}</div>
|
||||||
<hr style="margin-top:15px">
|
<hr style="margin-top:15px">
|
||||||
|
|
||||||
<h5>Champs</h5>
|
|
||||||
|
|
||||||
<ol class='fields'>
|
<div class="fields-wrapper">
|
||||||
{% foreach $form['fields'] as $field %}
|
<div class="header-fields">Champs</div>
|
||||||
<li class="odd-even">
|
|
||||||
<div class="title">
|
<ol class='fields'>
|
||||||
<div>
|
{% foreach $form['fields'] as $field %}
|
||||||
<strong style="font-family:monospace">{{ $field['name'] }}</strong>
|
<li class="odd-even">
|
||||||
<span style='margin-left:15px'>{{ $field['description'] }}</span>
|
<div class="title">
|
||||||
|
<div>
|
||||||
|
<strong style="font-family:monospace">{{ $field['name'] }} {% if $field['mandatory'] %}<span style="color:red">*</span>{% endif %}</strong>
|
||||||
|
<span style='margin-left:15px'>{{ $field['description'] }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
<div class="field-desc" style="margin-top:10px;;background:#fff;padding:5px;font-size:0.9em">
|
||||||
<div><u>Variable POST / champ JSON</u> : {{ $field['name'] }}</div>
|
<div><u>Variable POST / champ JSON</u> : {{ $field['name'] }}</div>
|
||||||
<div><u>Type(s)</u> : {{ $field['type'] }}</div>
|
<div><u>Type(s)</u> : {{ implode(' | ', array_map(fn($e) => $e->type, $field['type'])) }}</div>
|
||||||
<div><u>Nullable</u> : {{ yesOrNo($field['allowNulls']) }}</div>
|
<div><u>Nullable</u> : {{ yesOrNo($field['allowNulls']) }}</div>
|
||||||
<div><u>Pattern regex</u> : {{ $field['regexPattern'] }}</div>
|
{% if $field['regexPattern'] !== null %}
|
||||||
<div><u>Taille min.</u> : {{ $field['minLength'] }}</div>
|
<div><u>Pattern regex</u> : {{ $field['regexPattern'] }}</div>
|
||||||
<div><u>Taille max.</u> : {{ $field['maxLength'] }}</div>
|
{% endif %}
|
||||||
<div><u>Exemple</u> : {{ $field['example'] }}</div>
|
{% if $field['minLength'] !== null %}
|
||||||
</div>
|
<div><u>Taille min.</u> : {{ $field['minLength'] }}</div>
|
||||||
</li>
|
{% endif %}
|
||||||
{% endforeach %}
|
{% if $field['maxLength'] !== null %}
|
||||||
</ol>
|
<div><u>Taille max.</u> : {{ $field['maxLength'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if $field['example'] !== null || $field['values'] %}
|
||||||
|
<div><u>Exemple</u> : {{ $field['example'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if $field['default'] !== null %}
|
||||||
|
<div><u>Default</u> : {{ $field['default'] }}</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if $field['values'] %}
|
||||||
|
<div><u>Valeurs possibles</u> : [ <u>{{= implode('</u>, <u>', $field['values']) }}</u> ]</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
{% endforeach %}
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% or %}
|
||||||
|
<i style="color:#585858; padding:0 12px">{% _ "none" %}</i>
|
||||||
{% endforeach %}
|
{% endforeach %}
|
||||||
</div>
|
</div>
|
||||||
288
view/lean-api/request_debugger.phtml
Normal file
288
view/lean-api/request_debugger.phtml
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
{% use Ulmus\Api\Common\MethodEnum %}
|
||||||
|
|
||||||
|
{% language.set "lean.api.request.debugger" %}
|
||||||
|
|
||||||
|
<div id="request-debugger" class="hide">
|
||||||
|
<div class="request-compose">
|
||||||
|
<div class="loading-anim">
|
||||||
|
<div class="la-ball-spin-clockwise-fade-rotating">
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="request-head">
|
||||||
|
<div class="method" style="">
|
||||||
|
{% ui.select "method", MethodEnum::generateArray(), post('method') %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="url" style="flex-grow: 1;">
|
||||||
|
{% ui:text "url" %}
|
||||||
|
{% ui:text "token", $this->session->jwt, [ 'placeholder' => "JWT Token" ] %}
|
||||||
|
<button class="request-btn">Envoyer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="request-content">
|
||||||
|
<div id="request" style="min-height: 150px; width:100%; border:1px solid #ededed;">{}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="request-response hide">
|
||||||
|
<div class="response-head">
|
||||||
|
<span class="response-code"></span>
|
||||||
|
<span class="response-message"></span>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<pre id="response" class="code" ace-theme="ace/theme/cloud9_day"></pre>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.request-head {display:flex;padding-top: 4px;border:1px solid #e1e1e1;border-bottom:0}
|
||||||
|
.request-head .method {width: 90px;text-align: center;line-height: 34px;font-weight: bold;}
|
||||||
|
.request-head .method select {background:none;border:0;font-size: inherit;text-align: center;color: inherit;font-weight: bold;}
|
||||||
|
.request-head .url input {border:0;color: #6f6f6f;background: #f9f9f9;font-size: 80%;font-weight: bold;}
|
||||||
|
.request-head .url [name="url"] {width: calc(65% - 80px);}
|
||||||
|
.request-head .url [name="token"] {width:calc(35% - 10px);font-size:60%}
|
||||||
|
.request-head .url button {font-size: 80%;width: 80px;border-radius:0;border: 1px solid #ccc;height:26px;padding: 0;background:#f9f9f9;color: #6f6f6f;cursor: pointer;}
|
||||||
|
.request-head .url button:active {filter:contrast(85%);}
|
||||||
|
|
||||||
|
.response-head {display: flex;border:1px solid #ececec;margin-bottom: 5px;border-top:0}
|
||||||
|
.response-head .response-code {background:#dfdfdf;padding:0 8px;font-weight: bold;line-height: 29px;font-size: 80%;}
|
||||||
|
.response-head .response-message {padding:0 10px;background:#fbfbfb;line-height: 30px;font-size: 80%;}
|
||||||
|
|
||||||
|
.request-compose {position:relative;}
|
||||||
|
.loading-anim {display:none;position:absolute;left:0;right:0;top:0;bottom:0;justify-content: center;align-items: center;z-index: 15;background: rgba(122, 121, 121, 0.25);}
|
||||||
|
.la-ball-spin-clockwise-fade-rotating {color: #0f6ab4!important;}
|
||||||
|
.loading .loading-anim {display: flex;}
|
||||||
|
|
||||||
|
#response {max-height: 66vh;}
|
||||||
|
/*!
|
||||||
|
* Load Awesome v1.1.0 (http://github.danielcardoso.net/load-awesome/)
|
||||||
|
* Copyright 2015 Daniel Cardoso <@DanielCardoso>
|
||||||
|
* Licensed under MIT
|
||||||
|
*/
|
||||||
|
.la-ball-spin-clockwise-fade-rotating,.la-ball-spin-clockwise-fade-rotating>div{position:relative;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.la-ball-spin-clockwise-fade-rotating{display:block;font-size:0;color:#fff}.la-ball-spin-clockwise-fade-rotating.la-dark{color:#333}.la-ball-spin-clockwise-fade-rotating>div{display:inline-block;float:none;background-color:currentColor;border:0 solid currentColor}.la-ball-spin-clockwise-fade-rotating{width:32px;height:32px;-webkit-animation:ball-spin-clockwise-fade-rotating-rotate 6s infinite linear;-moz-animation:ball-spin-clockwise-fade-rotating-rotate 6s infinite linear;-o-animation:ball-spin-clockwise-fade-rotating-rotate 6s infinite linear;animation:ball-spin-clockwise-fade-rotating-rotate 6s infinite linear}.la-ball-spin-clockwise-fade-rotating>div{position:absolute;top:50%;left:50%;width:8px;height:8px;margin-top:-4px;margin-left:-4px;border-radius:100%;-webkit-animation:ball-spin-clockwise-fade-rotating 1s infinite linear;-moz-animation:ball-spin-clockwise-fade-rotating 1s infinite linear;-o-animation:ball-spin-clockwise-fade-rotating 1s infinite linear;animation:ball-spin-clockwise-fade-rotating 1s infinite linear}.la-ball-spin-clockwise-fade-rotating>div:nth-child(1){top:5%;left:50%;-webkit-animation-delay:-.875s;-moz-animation-delay:-.875s;-o-animation-delay:-.875s;animation-delay:-.875s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(2){top:18.1801948466%;left:81.8198051534%;-webkit-animation-delay:-.75s;-moz-animation-delay:-.75s;-o-animation-delay:-.75s;animation-delay:-.75s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(3){top:50%;left:95%;-webkit-animation-delay:-.625s;-moz-animation-delay:-.625s;-o-animation-delay:-.625s;animation-delay:-.625s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(4){top:81.8198051534%;left:81.8198051534%;-webkit-animation-delay:-.5s;-moz-animation-delay:-.5s;-o-animation-delay:-.5s;animation-delay:-.5s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(5){top:94.9999999966%;left:50.0000000005%;-webkit-animation-delay:-.375s;-moz-animation-delay:-.375s;-o-animation-delay:-.375s;animation-delay:-.375s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(6){top:81.8198046966%;left:18.1801949248%;-webkit-animation-delay:-.25s;-moz-animation-delay:-.25s;-o-animation-delay:-.25s;animation-delay:-.25s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(7){top:49.9999750815%;left:5.0000051215%;-webkit-animation-delay:-.125s;-moz-animation-delay:-.125s;-o-animation-delay:-.125s;animation-delay:-.125s}.la-ball-spin-clockwise-fade-rotating>div:nth-child(8){top:18.179464974%;left:18.1803700518%;-webkit-animation-delay:0s;-moz-animation-delay:0s;-o-animation-delay:0s;animation-delay:0s}.la-ball-spin-clockwise-fade-rotating.la-sm{width:16px;height:16px}.la-ball-spin-clockwise-fade-rotating.la-sm>div{width:4px;height:4px;margin-top:-2px;margin-left:-2px}.la-ball-spin-clockwise-fade-rotating.la-2x{width:64px;height:64px}.la-ball-spin-clockwise-fade-rotating.la-2x>div{width:16px;height:16px;margin-top:-8px;margin-left:-8px}.la-ball-spin-clockwise-fade-rotating.la-3x{width:96px;height:96px}.la-ball-spin-clockwise-fade-rotating.la-3x>div{width:24px;height:24px;margin-top:-12px;margin-left:-12px}@-webkit-keyframes ball-spin-clockwise-fade-rotating-rotate{100%{-webkit-transform:rotate(-360deg);transform:rotate(-360deg)}}@-moz-keyframes ball-spin-clockwise-fade-rotating-rotate{100%{-moz-transform:rotate(-360deg);transform:rotate(-360deg)}}@-o-keyframes ball-spin-clockwise-fade-rotating-rotate{100%{-o-transform:rotate(-360deg);transform:rotate(-360deg)}}@keyframes ball-spin-clockwise-fade-rotating-rotate{100%{-webkit-transform:rotate(-360deg);-moz-transform:rotate(-360deg);-o-transform:rotate(-360deg);transform:rotate(-360deg)}}@-webkit-keyframes ball-spin-clockwise-fade-rotating{50%{opacity:.25;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@-moz-keyframes ball-spin-clockwise-fade-rotating{50%{opacity:.25;-moz-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-moz-transform:scale(1);transform:scale(1)}}@-o-keyframes ball-spin-clockwise-fade-rotating{50%{opacity:.25;-o-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-o-transform:scale(1);transform:scale(1)}}@keyframes ball-spin-clockwise-fade-rotating{50%{opacity:.25;-webkit-transform:scale(.5);-moz-transform:scale(.5);-o-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script src="{% asset 'static/ace/src-noconflict/ace.js' %}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
<script src="{% asset 'static/ace/src-noconflict/ext-static_highlight.js' %}" type="text/javascript" charset="utf-8"></script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
let requestDebugger = document.getElementById('request-debugger'),
|
||||||
|
requestHead = requestDebugger.querySelector(".request-head"),
|
||||||
|
requestContent = requestDebugger.querySelector(".request-content"),
|
||||||
|
responseHead = requestDebugger.querySelector(".response-head"),
|
||||||
|
responseResponse = requestDebugger.querySelector(".request-response"),
|
||||||
|
method = requestHead.querySelector(".method"),
|
||||||
|
input = requestHead.querySelector("[name='url']"),
|
||||||
|
token = requestHead.querySelector("[name='token']"),
|
||||||
|
tokenValue = sessionStorage.getItem("token"),
|
||||||
|
button = requestHead.querySelector(".request-btn");
|
||||||
|
|
||||||
|
// Editor
|
||||||
|
let editor = ace.edit("request");
|
||||||
|
editor.setTheme("ace/theme/cloud9_day");
|
||||||
|
editor.session.setMode("ace/mode/json");
|
||||||
|
|
||||||
|
// Code highlighter
|
||||||
|
let highlight = ace.require("ace/ext/static_highlight"),
|
||||||
|
dom = ace.require("ace/lib/dom"),
|
||||||
|
responseEditorElement = responseResponse.querySelector('#response');
|
||||||
|
|
||||||
|
input.addEventListener('keydown', evt => evt.keyCode === 13 ? button.click() : null);
|
||||||
|
token.addEventListener('change', evt => sessionStorage.setItem("token", evt.target.value));
|
||||||
|
|
||||||
|
if (tokenValue) {
|
||||||
|
token.value = tokenValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", (evt) => {
|
||||||
|
document.querySelectorAll(".form-name").forEach(elem => {
|
||||||
|
elem.insertAdjacentHTML('beforeend', '<a href="javascript:void(0)" class="btn debug">DEBUG</a>');
|
||||||
|
|
||||||
|
let formData = JSON.parse(elem.closest('.single-form').getAttribute('data-form'));
|
||||||
|
|
||||||
|
elem.querySelector(".btn.debug").addEventListener("click", (btn) => {
|
||||||
|
requestDebugger.classList.toggle('hide', false);
|
||||||
|
|
||||||
|
let json = {};
|
||||||
|
|
||||||
|
formData.fields.forEach((field) => {
|
||||||
|
const value = field.default ? field.default : ( field.allowNulls ? null : "" );
|
||||||
|
|
||||||
|
json[field.name] = value === "[]" ? [] : value;
|
||||||
|
});
|
||||||
|
|
||||||
|
editor.setValue(JSON.stringify(json, null, 2), -1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
document.querySelectorAll("li[class*='method-']").forEach( (url) => {
|
||||||
|
url.addEventListener("click", (evt) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
|
||||||
|
let routeMethod = url.querySelector('.route-method');
|
||||||
|
|
||||||
|
method.style.color = getComputedStyle(routeMethod).getPropertyValue('background-color');
|
||||||
|
method.querySelector('select').value = routeMethod.innerText;
|
||||||
|
|
||||||
|
input.value = url.querySelector('.route-link a').getAttribute('href');
|
||||||
|
|
||||||
|
document.getElementById('request-debugger').classList.toggle('hide', false);
|
||||||
|
|
||||||
|
replaceUrlVariableUsingData();
|
||||||
|
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let aceMode;
|
||||||
|
|
||||||
|
button.addEventListener("click", (evt) => {
|
||||||
|
let requestMethod = method.querySelector('select').value;
|
||||||
|
|
||||||
|
if (requestMethod === 'DELETE' && ! confirm("Attention ! Vous allez lancer une procédure de suppression de données.\n\nContinuer ?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
launchRequest(requestMethod, input.value, editor.getValue())
|
||||||
|
.then((response) => {
|
||||||
|
console.log(response);
|
||||||
|
responseHead.querySelector('.response-code').innerText = response.status;
|
||||||
|
responseHead.querySelector('.response-message').innerText = response.statusText;
|
||||||
|
|
||||||
|
aceMode = parseContentType(response);
|
||||||
|
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(body => {
|
||||||
|
console.log(aceMode);
|
||||||
|
if (aceMode === "json") {
|
||||||
|
body = JSON.stringify(JSON.parse(body), null, 2);
|
||||||
|
responseEditorElement.innerHTML = body;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
responseEditorElement.innerText = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatResponse("ace/mode/" + aceMode);
|
||||||
|
}).finally(() => requestDebugger.classList.remove("loading"));
|
||||||
|
|
||||||
|
responseResponse.classList.toggle('hide', false);
|
||||||
|
});
|
||||||
|
|
||||||
|
let selectRoute = getQueryVariable("debug.route"),
|
||||||
|
presetData = getQueryVariable("debug.data");
|
||||||
|
|
||||||
|
if ( selectRoute ) {
|
||||||
|
if (presetData) {
|
||||||
|
editor.setValue(presetData);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(`[data-name="${selectRoute}"]`).click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function launchRequest(method = "POST", url = "", body = "{}") {
|
||||||
|
let headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (token.value) {
|
||||||
|
headers['Authorization'] = `Bearer ${token.value}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
method = method.toUpperCase();
|
||||||
|
|
||||||
|
let responseData = {
|
||||||
|
method: method,
|
||||||
|
mode: "cors",
|
||||||
|
cache: "no-cache",
|
||||||
|
credentials: headers.Authorization ? "omit" : "same-origin",
|
||||||
|
headers: headers,
|
||||||
|
redirect: "follow",
|
||||||
|
referrerPolicy: "no-referrer"
|
||||||
|
};
|
||||||
|
|
||||||
|
if ( [ "HEAD", "GET" ].indexOf(method) === -1 ) {
|
||||||
|
responseData.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestDebugger.classList.add("loading");
|
||||||
|
|
||||||
|
return await fetch(url, responseData);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatResponse(mode) {
|
||||||
|
highlight(responseEditorElement, {
|
||||||
|
mode: mode,
|
||||||
|
theme: responseEditorElement.getAttribute("ace-theme"),
|
||||||
|
firstLineNumber: 1,
|
||||||
|
showGutter: responseEditorElement.getAttribute("ace-gutter"),
|
||||||
|
trim: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseContentType(response) {
|
||||||
|
let type = response.headers.get('content-type').toLowerCase();
|
||||||
|
|
||||||
|
if ( type.indexOf('text/html') !== -1 ) {
|
||||||
|
return "html";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('text/css') !== -1 ) {
|
||||||
|
return "css";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('text/csv') !== -1 ) {
|
||||||
|
return "csv";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('text/xml') !== -1 ) {
|
||||||
|
return "xml";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('text/plain') !== -1 ) {
|
||||||
|
return "text";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('application/javascript') !== -1 ) {
|
||||||
|
return "javascript";
|
||||||
|
}
|
||||||
|
else if ( type.indexOf('application/json') !== -1 || type.indexOf('application/ld+json') !== -1 ) {
|
||||||
|
return "json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryVariable(variable) {
|
||||||
|
retval = false;
|
||||||
|
|
||||||
|
window.location.search.substring(1).split("&").forEach(vars => {
|
||||||
|
var pair = vars.split("=");
|
||||||
|
|
||||||
|
if (pair.length === 2 && pair[0] === variable) {
|
||||||
|
return retval = decodeURI(pair[1]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceUrlVariableUsingData() {
|
||||||
|
try {
|
||||||
|
let vars = input.value.match(/[^{}]+(?=})/g),
|
||||||
|
body = JSON.parse(editor.getValue());
|
||||||
|
|
||||||
|
vars.forEach(v => {
|
||||||
|
if (body[v]) {
|
||||||
|
input.value = input.value.replace("{" + v + "}", body[v]);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
catch(e) {}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,31 +1,38 @@
|
|||||||
{% function printMethods(array|string $methods) %}
|
{% language.set "lean.api.descriptor.route" %}
|
||||||
{% foreach (array) $methods as $method %}
|
|
||||||
<a style="color:#ac1b1b" href="#" class="method-item" data-method="{{ $method }}">{{ $method }}</a>
|
|
||||||
{% endforeach %}
|
|
||||||
{% endfunction %}
|
|
||||||
|
|
||||||
<ul>
|
<ul class="routes-wrapper">
|
||||||
{% foreach $routes as $route %}
|
{% foreach $routes as $route %}
|
||||||
<li>
|
{% foreach $route['methods'] as $method %}
|
||||||
<span><a href="{{ $route['route'] }}" title="{{ $route['path'] }}" style='font-family:monospace;font-size:.85em'>{{ $route['cleaned'] }}</a> - {{= $route['description'] }}</span>
|
<li class="method-{{ strtolower($method) }}" data-name="{{ $route['name'] }}">
|
||||||
<span>{{ printMethods($route['methods']) }}</span>
|
<span class="route-method">
|
||||||
<small style="color:#374300">{{ $route['name'] }}</small>
|
<span class="method-name">{{ strtoupper($method) }}</span>
|
||||||
</li>
|
</span>
|
||||||
|
|
||||||
|
<span class="route-link">
|
||||||
|
<a href="{{ $route['route'] }}" title="{{ $route['path'] }}">{{ $route['cleaned'] }}</a>
|
||||||
|
{% if ! empty($route['description']) %}
|
||||||
|
<span>-</span>
|
||||||
|
<span>{{= $route['description'] }}</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<small class="route-name">
|
||||||
|
<span>{{ $route['name'] }}</span>
|
||||||
|
<br>
|
||||||
|
<span class="permissions">
|
||||||
|
à venir
|
||||||
|
{# foreach ['admin', 'school' ] as $privilege %}
|
||||||
|
<u class="privilege">{{ $privilege }}</u>
|
||||||
|
{% endforeach #}
|
||||||
|
</span>
|
||||||
|
</small>
|
||||||
|
</li>
|
||||||
|
{% or %}
|
||||||
|
<i style="color:#585858; padding:0 12px">{% _ "none" %}</i>
|
||||||
|
{% endforeach %}
|
||||||
{% endforeach %}
|
{% endforeach %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<div class="api-console">
|
<style>
|
||||||
<label>Corps / body (JSON)</label>
|
.permissions {color:#888}
|
||||||
<ui-textarea name="breeder_description">
|
</style>
|
||||||
<div slot="input">
|
|
||||||
{% ui:textarea "body" %}
|
|
||||||
</div>
|
|
||||||
</ui-textarea>
|
|
||||||
<div></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="text/javascript">
|
|
||||||
document.querySelectorAll('[data-method]').forEach((e) => {
|
|
||||||
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -16,35 +16,82 @@
|
|||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
*, *:before, *:after {box-sizing: border-box;}
|
||||||
a{color:#e44a00}h3{background:#e1e1e1;padding:6px 12px}ul{background:#e9e9fd;padding-top:20px;padding-bottom:20px;border:1px solid #e3e3ec}ul li + li{margin-top:8px}li > em{font-size:0.75rem;color:gray}body{font-family:Helvetica, 'Helvetica Neuve', Arial, Tahoma, sans-serif;font-size:17px;color:#333}h1,h2,h3,h4,h5,h6{color:#222;margin:0 0 20px}dl,ol,p,pre,table,ul{margin:0 0 20px}h1,h2,h3{line-height:1.1}h1{font-size:20px;text-align:right;color:#387eea;font-weight:bold}h2{color:#393939}h3,h4,h5,h6{color:#494949}h3{display:flex}h3 > code{margin-right:5px;color:#b52dac}h3 > strong{margin-left:auto}a{color:#39c;font-weight:400;text-decoration:none}a small{font-size:11px;color:#777;margin-top:-0.6em;display:block}.wrapper{width:860px;margin:0 auto}blockquote{border-left:1px solid #e5e5e5;margin:0;padding:0 0 0 20px;font-style:italic}code,pre{font-size:12px}pre{padding:8px 15px;background:#f8f8f8;border-radius:5px;border:1px solid #e5e5e5;overflow-x:auto}table{width:100%;border-collapse:collapse}td,th{text-align:left;padding:5px 10px;border-bottom:1px solid #e5e5e5}dt{color:#444;font-weight:700}th{color:#444}img{max-width:100%}header{width:270px;float:left;position:fixed}header ul{list-style:none;height:40px;padding:0;background:#eee;background:-moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));background:-webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:linear-gradient(top, #f8f8f8 0%,#dddddd 100%);border-radius:5px;border:1px solid #d2d2d2;box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;width:270px}header li{width:89px;float:left;border-right:1px solid #d2d2d2;height:40px}header ul a{line-height:1;font-size:11px;color:#999;display:block;text-align:center;padding-top:6px;height:40px}strong{color:#222;font-weight:700}header ul li + li{width:88px;border-left:1px solid #fff}header ul li + li + li{border-right:none;width:89px}header ul a strong{font-size:14px;display:block;color:#222}section{width:500px;float:right;padding-bottom:50px}small{font-size:11px}hr{border:0;background:#e5e5e5;height:1px;margin:0 0 20px}footer{width:270px;float:left;position:fixed;bottom:50px}@media print, screen and (max-width: 960px){div.wrapper{width:auto;margin:0}footer,header,section{float:none;position:static;width:auto}header{padding-right:320px}section{border:1px solid #e5e5e5;border-width:1px 0;padding:20px 0;margin:0 0 20px}header a small{display:inline}header ul{position:absolute;right:50px;top:52px}}@media print, screen and (max-width: 720px){body{word-wrap:break-word}header{padding:0}header p.view,header ul{position:static}code,pre{word-wrap:normal}}@media print, screen and (max-width: 480px){body{padding:15px}header ul{display:none}}@media print{body{padding:0.4in;font-size:12pt;color:#444}}#wrapper{margin-left:auto;margin-right:auto;background-color:white}.ca-menu{list-style:none;padding:0;margin:20px auto}#navi{padding-top:15px;padding-right:15px;float:right;width:420px}#title{padding-left:15px;width:460px;float:left}div.clear{clear:both}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.2em}h5{font-size:1em;font-weight:bold}h6{font-size:1em;font-weight:bold}h1,h2,h3,h4,h5,h6{font-weight:normal;line-height:2.5rem;margin:1rem 0}.post p{max-width:580px}ol.list,ul.list{padding-left:3.333em;max-width:580px}.post h2{border-bottom:1px solid #EDEDED}h1:nth-child(1),h2:nth-child(1),h3:nth-child(1),h4:nth-child(1),h5:nth-child(1),h6:nth-child(1){margin-top:0}body{padding:1em}#wrapper{padding:1em}@media (min-width: 43.75em){body{padding:2em}#wrapper{padding:2em}}@media (min-width: 62em){body{padding:3em}#wrapper{max-width:740px;padding:3em}}
|
a{color:#e44a00}h3{background:#e1e1e1;padding:6px 12px}ul{background:#e9e9fd;padding-top:20px;padding-bottom:20px;border:1px solid #e3e3ec}ul li + li{margin-top:8px}li > em{font-size:0.75rem;color:gray}body{font-family:Helvetica, 'Helvetica Neuve', Arial, Tahoma, sans-serif;font-size:17px;color:#333}h1,h2,h3,h4,h5,h6{color:#222;margin:0 0 20px}dl,ol,p,pre,table,ul{margin:0 0 20px}h1,h2,h3{line-height:1.1}h1{font-size:20px;text-align:right;color:#387eea;font-weight:bold}h2{color:#393939}h3,h4,h5,h6{color:#494949}h3{display:flex}h3 > code{margin-right:5px;color:#b52dac}h3 > strong{margin-left:auto}a{color:#39c;font-weight:400;text-decoration:none}a small{font-size:11px;color:#777;margin-top:-0.6em;display:block}.wrapper{width:860px;margin:0 auto}blockquote{border-left:1px solid #e5e5e5;margin:0;padding:0 0 0 20px;font-style:italic}code,pre{font-size:12px}pre{padding:8px 15px;background:#f8f8f8;border-radius:5px;border:1px solid #e5e5e5;overflow-x:auto}table{width:100%;border-collapse:collapse}td,th{text-align:left;padding:5px 10px;border-bottom:1px solid #e5e5e5}dt{color:#444;font-weight:700}th{color:#444}img{max-width:100%}header{width:270px;float:left;position:fixed}header ul{list-style:none;height:40px;padding:0;background:#eee;background:-moz-linear-gradient(top, #f8f8f8 0%, #dddddd 100%);background:-webkit-gradient(linear, left top, left bottom, color-stop(0%,#f8f8f8), color-stop(100%,#dddddd));background:-webkit-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-o-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:-ms-linear-gradient(top, #f8f8f8 0%,#dddddd 100%);background:linear-gradient(top, #f8f8f8 0%,#dddddd 100%);border-radius:5px;border:1px solid #d2d2d2;box-shadow:inset #fff 0 1px 0, inset rgba(0,0,0,0.03) 0 -1px 0;width:270px}header li{width:89px;float:left;border-right:1px solid #d2d2d2;height:40px}header ul a{line-height:1;font-size:11px;color:#999;display:block;text-align:center;padding-top:6px;height:40px}strong{color:#222;font-weight:700}header ul li + li{width:88px;border-left:1px solid #fff}header ul li + li + li{border-right:none;width:89px}header ul a strong{font-size:14px;display:block;color:#222}section{width:500px;float:right;padding-bottom:50px}small{font-size:11px}hr{border:0;background:#e5e5e5;height:1px;margin:0 0 20px}footer{width:270px;float:left;position:fixed;bottom:50px}@media print, screen and (max-width: 960px){div.wrapper{width:auto;margin:0}footer,header,section{float:none;position:static;width:auto}header{padding-right:320px}section{border:1px solid #e5e5e5;border-width:1px 0;padding:20px 0;margin:0 0 20px}header a small{display:inline}header ul{position:absolute;right:50px;top:52px}}@media print, screen and (max-width: 720px){body{word-wrap:break-word}header{padding:0}header p.view,header ul{position:static}code,pre{word-wrap:normal}}@media print, screen and (max-width: 480px){body{padding:15px}header ul{display:none}}@media print{body{padding:0.4in;font-size:12pt;color:#444}}#wrapper{margin-left:auto;margin-right:auto;background-color:white}.ca-menu{list-style:none;padding:0;margin:20px auto}#navi{padding-top:15px;padding-right:15px;float:right;width:420px}#title{padding-left:15px;width:460px;float:left}div.clear{clear:both}h2{font-size:2em}h3{font-size:1.5em}h4{font-size:1.2em}h5{font-size:1em;font-weight:bold}h6{font-size:1em;font-weight:bold}h1,h2,h3,h4,h5,h6{font-weight:normal;line-height:2.5rem;margin:1rem 0}.post p{max-width:580px}ol.list,ul.list{padding-left:3.333em;max-width:580px}.post h2{border-bottom:1px solid #EDEDED}h1:nth-child(1),h2:nth-child(1),h3:nth-child(1),h4:nth-child(1),h5:nth-child(1),h6:nth-child(1){margin-top:0}body{padding:1em}#wrapper{padding:1em}@media (min-width: 43.75em){body{padding:2em}#wrapper{padding:2em}}@media (min-width: 62em){body{padding:3em}#wrapper{max-width:740px;padding:3em}}
|
||||||
ol{background: #eff4f2;padding-top:20px;padding-bottom:20px;border:1px solid #e3e3ec}
|
ol{background: #eff4f2;/*padding-top:20px;padding-bottom:20px;*/border:1px solid #e3e3ec}
|
||||||
h4{background:#e0f7ed;padding:6px 12px; font-weight: bold!important;font-size:100%;margin-top:0}
|
h4{background:#e0f7ed;padding:6px 12px; font-weight: bold!important;font-size:100%;margin-top:0}
|
||||||
h5{text-decoration: underline}
|
h5{text-decoration: underline}
|
||||||
h3 {display: flex;align-items: center;height: 60px;padding: 0 15px 0 15px;font-variant: small-caps;}
|
h3 {display: flex;align-items: center;padding: 0 15px 0 15px;font-variant: small-caps;border-bottom: 5px solid #ccc;line-height: 60px;}
|
||||||
li.odd-even{border-top:1px solid #ccc;margin:10px 0;padding:15px 15px 10px 5px}
|
li.odd-even{border-top:1px solid #ccc;margin:10px 0;padding:15px 15px 10px 5px}
|
||||||
li.odd-even:first-child{border:0}
|
li.odd-even:first-child{border:0}
|
||||||
input, button {padding:5px; font-size:1em;margin-top:10px}
|
input, button {padding:5px; font-size:1em;margin:0}
|
||||||
|
|
||||||
|
.hide {display:none!important}
|
||||||
|
|
||||||
ul {background:#f4f4f4; list-style: none; padding-left:20px}
|
ul {background:#f4f4f4; list-style: none; padding-left:20px}
|
||||||
ul ul {margin: 0;border: 0;padding: 5px 30px;}
|
ul ul {margin: 0;border: 0;padding: 5px 30px;}
|
||||||
ul li + li {margin-top: 12px;}
|
|
||||||
ul ul > li:before {content:"↳"}
|
ul ul > li:before {content:"↳"}
|
||||||
ol .title { display: flex;justify-content: space-between;background: #ffffffb2;padding: 9px 5px;border: 1px solid #fff;}
|
ol .title { display: flex;justify-content: space-between;background: #ffffffb2;padding: 9px 5px;border: 1px solid #fff;}
|
||||||
.field-desc > div {padding:5px;}
|
.field-desc > div {padding:5px;}
|
||||||
|
.header-fields {color: #fff;line-height: 1.8rem;font-variant: small-caps;padding: 4px 0.8rem 0 0.8rem}
|
||||||
.api-console {padding:20px 15px;border:1px solid #aeaeae;border-left-width: 5px;background:#ccc}
|
/*.fields-wrapper {border-left: 3px solid #c14141; margin-left: -10px; padding-left: 10px;}*/
|
||||||
|
|
||||||
.forms ol {background: #ccdef2;}
|
.forms ol {background: #ccdef2;}
|
||||||
.forms li {border-color: #859aae;}
|
.forms li {border-color: #859aae;}
|
||||||
.forms .form-name {background: #9cc5e6;color: #284168;font-size:110%}
|
.forms .form-name {background: #9cc5e6;display:flex;justify-content: space-between}
|
||||||
|
.forms .form-name span {color: #284168;font-size:110%}
|
||||||
|
.forms .form-name .btn {background:rgba(50,50,50,0.5);padding:0 15px;border: 1px solid rgba(50,50,50,0.8);font-family: 'Déja Vu', 'Courier New', Courier, monospace, serif;font-size: 90%;color: #fff;text-decoration: underline;}
|
||||||
|
.forms .header-fields {background: #677b8c;}
|
||||||
|
.forms .fields-wrapper{border-color:#677b8c}
|
||||||
|
|
||||||
|
.routes-wrapper {padding:5px}
|
||||||
|
.routes-wrapper li {display:flex;border: 1px solid #ccc;align-items: stretch;cursor:pointer}
|
||||||
|
.routes-wrapper li:hover {filter: contrast(85%)}
|
||||||
|
.routes-wrapper li + li {margin-top: 5px;}
|
||||||
|
.routes-wrapper .route-method {display:flex;align-items: center; justify-content: center; line-height: 1.8rem;padding:6px 5px 0 5px;min-width:80px;text-align:center;font-weight:bold;color:#fff;}
|
||||||
|
.routes-wrapper .route-link {line-height: 1.8rem;padding:0 10px;display: flex;align-items: center;}
|
||||||
|
.routes-wrapper .route-link span {margin-left: 7px;}
|
||||||
|
.routes-wrapper .route-link a {font-family:monospace;font-size:.85em;white-space: nowrap;}
|
||||||
|
.routes-wrapper .route-name {margin-left:auto;font-weight:bold;min-width: 20%;text-align: right;background:rgba(0, 0, 0, 0.02);line-height: 1.4rem;padding:6px 7px 0 5px;align-content: center;}
|
||||||
|
.routes-wrapper li.method-get {background:#e7eff7;border-color: #bfcfdd;}
|
||||||
|
.routes-wrapper li.method-get .route-method {background:#0f6ab4;}
|
||||||
|
.routes-wrapper li.method-get .route-link a, .routes-wrapper li.method-get .route-name {color: #0f6ab4;}
|
||||||
|
.routes-wrapper li.method-head {background:#f5e8f2;border-color:#ffc0e7;}
|
||||||
|
.routes-wrapper li.method-head .route-method {background:#ca6aa5;}
|
||||||
|
.routes-wrapper li.method-head .route-link a, .routes-wrapper li.method-head .route-name {color: #ca6aa5;}
|
||||||
|
.routes-wrapper li.method-post {background:#e6f5eb;border-color: #ace3c2;}
|
||||||
|
.routes-wrapper li.method-post .route-method {background:#10a44a;}
|
||||||
|
.routes-wrapper li.method-post .route-link a, .routes-wrapper li.method-post .route-name {color: #10a44a;}
|
||||||
|
.routes-wrapper li.method-patch {background:#fce9e3;border-color:#ffd4b3}
|
||||||
|
.routes-wrapper li.method-patch .route-method {background:#f4842f;}
|
||||||
|
.routes-wrapper li.method-patch .route-link a, .routes-wrapper li.method-patch .route-name {color: #f4842f;}
|
||||||
|
.routes-wrapper li.method-option {background:#eaeaea;border-color: #dbdbdb;}
|
||||||
|
.routes-wrapper li.method-option .route-method {background:#a4a4a4;}
|
||||||
|
.routes-wrapper li.method-option .route-link a, .routes-wrapper li.method-option .route-name {color: #717171;}
|
||||||
|
.routes-wrapper li.method-delete {background:#f5e8e8;border-color: #ffadaf;}
|
||||||
|
.routes-wrapper li.method-delete .route-method {background:#d73c41;}
|
||||||
|
.routes-wrapper li.method-delete .route-link a, .routes-wrapper li.method-delete .route-name {color: #d73c41;}
|
||||||
|
.routes-wrapper li.method-put {background:#fff5c2;border-color: #eadd9a;}
|
||||||
|
.routes-wrapper li.method-put .route-method {background:#d7bf3c;}
|
||||||
|
.routes-wrapper li.method-put .route-link a, .routes-wrapper li.method-put .route-name {color: #aa941c;}
|
||||||
|
|
||||||
|
.forms{background: #e8f7ff;padding: 5px;}
|
||||||
|
|
||||||
|
.entities {background: #f0e0e0;padding:5px;}
|
||||||
.entity-wrapper .entity-name {background:#eaa1af;color: #682828;font-size:110%}
|
.entity-wrapper .entity-name {background:#eaa1af;color: #682828;font-size:110%}
|
||||||
.entity-wrapper ol {background: #f0ddcd;}
|
.entity-wrapper .fields-wrapper {border-color: #c14141}
|
||||||
|
.entity-wrapper .header-fields {background:#c14141}
|
||||||
|
.entity-wrapper ol {background: #e3d0d0;}
|
||||||
.entity-wrapper li {border-color: #ae8585;}
|
.entity-wrapper li {border-color: #ae8585;}
|
||||||
|
.entity-wrapper li .default {color:#bf7d4d;}
|
||||||
|
|
||||||
.search-request-name {background: #b6e6ae;color: #395332;font-size:110%}
|
.search-request-name {background: #b6e6ae;color: #395332;font-size:110%}
|
||||||
.search-request-wrapper {padding-left: 15px;border-left: 3px solid #72886a;}
|
.search-request-wrapper {padding: 15px;margin-top:5px;border-left: 3px solid #72886a;background: #eeffe6;}
|
||||||
|
.search-request-wrapper .header-fields {background:#395332}
|
||||||
|
.search-request-wrapper .fields-wrapper{border-color:#395332}
|
||||||
.search-request-wrapper ol {background: #d8f0cd;}
|
.search-request-wrapper ol {background: #d8f0cd;}
|
||||||
.search-request-wrapper li {border-color: #72886a;}
|
.search-request-wrapper li {border-color: #72886a;}
|
||||||
|
.search-request-wrapper .default {color:#72886a;}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
{% endsection %}
|
{% endsection %}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user