- Lean's API WIP on forms and ApiRenderer
This commit is contained in:
1 parent
3ce356e1f2
commit
4d7053a9cb
30 files changed
+1003
-56
No files matched your search
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api;
|
||||
|
||||
use League\Route\Strategy;
|
||||
|
||||
use League\Route\Http\Exception\NotFoundException;
|
||||
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 Strategy\ApplicationStrategy {
|
||||
|
||||
public function __construct(
|
||||
protected HttpFactoryInterface $httpFactory,
|
||||
ContainerInterface $di,
|
||||
) {
|
||||
$this->container = $di;
|
||||
}
|
||||
|
||||
public function getThrowableHandler(): MiddlewareInterface
|
||||
{
|
||||
return new class implements MiddlewareInterface
|
||||
{
|
||||
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();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function getNotFoundDecorator(NotFoundException $exception): MiddlewareInterface
|
||||
{
|
||||
return new class($this->httpFactory) implements MiddlewareInterface {
|
||||
|
||||
public function __construct(
|
||||
protected HttpFactoryInterface $httpFactory,
|
||||
) {}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||
{
|
||||
if (php_sapi_name() !== 'cli' and ! defined('STDIN')) {
|
||||
return $this->throw404($request);
|
||||
}
|
||||
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
public function throw404(ServerRequestInterface $request) : ResponseInterface
|
||||
{
|
||||
return $this->httpFactory->createJsonResponse([
|
||||
'status' => 'error',
|
||||
'message' => "Not Found",
|
||||
'ts' => time(),
|
||||
], 404);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,5 +12,6 @@ class ContextField
|
||||
public ?int $maxLength = null,
|
||||
public ?string $regexFormat = null,
|
||||
public ?string $example = null,
|
||||
public bool $hidden = false,
|
||||
) {}
|
||||
}
|
||||
@@ -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(getenv("LEAN_API_PROJECT_PATH") . "/meta/docs/lean-api.md", [ Entity\Error::class, ], [ Form\Debug\ErrorContext::class ]);
|
||||
}
|
||||
}
|
||||
@@ -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(getenv("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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Entity;
|
||||
|
||||
use Lean\Api\Attribute\EntityField;
|
||||
use Ulmus\EntityTrait,
|
||||
Ulmus\SearchRequest\SearchableInterface,
|
||||
Ulmus\SearchRequest\SearchRequestInterface,
|
||||
Ulmus\Entity\Field\Datetime;
|
||||
|
||||
use Ulmus\{Attribute\Obj\Table,
|
||||
Entity\EntityInterface};
|
||||
|
||||
use Ulmus\{Api\Common\MethodEnum,
|
||||
SearchRequest,
|
||||
SearchRequest\Attribute\PropertyValueModifier\Split,
|
||||
SearchRequest\Attribute\PropertyValueSource,
|
||||
SearchRequest\Attribute\SearchOrderBy,
|
||||
SearchRequest\Attribute\SearchWhere,
|
||||
SearchRequest\SearchMethodEnum};
|
||||
|
||||
use Ulmus\Attribute\Property\{Field, Filter, FilterJoin, Relation, Join, Virtual, Where};
|
||||
|
||||
#[Table(name: "debug_errors", adapter: "lean.api")]
|
||||
class Error implements EntityInterface, \JsonSerializable {
|
||||
use EntityTrait;
|
||||
|
||||
#[Field\Id]
|
||||
public int $id;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "URL from which the error occurred")]
|
||||
public string $url;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "HTTP method", field: "httpMethod")]
|
||||
public MethodEnum $method;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "Error message")]
|
||||
public string $message;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "Error code")]
|
||||
public int $code;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "File from which the error occurred")]
|
||||
public string $file;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "Line in the file from which the error occurred")]
|
||||
public int $line;
|
||||
|
||||
#[Field]
|
||||
#[EntityField(description: "Stack trace of the exception")]
|
||||
public array $trace;
|
||||
|
||||
#[Field\CreatedAt(name: "created_at")]
|
||||
public Datetime $createdAt;
|
||||
|
||||
public function __toString() {
|
||||
return mb_strlen($this->message) > 255 ? mb_substr($this->message, 0, 255) . "[...]" : $this->message;
|
||||
}
|
||||
|
||||
public static function searchRequest(...$arguments) : SearchRequestInterface
|
||||
{
|
||||
return new #[SearchRequest\Attribute\SearchRequestParameter(Error::class)] class(...$arguments) extends SearchRequest\SearchRequest {
|
||||
#[SearchRequest\Attribute\SearchWhere(source: SearchRequest\Attribute\PropertyValueSource::RequestAttribute)]
|
||||
public int $id;
|
||||
|
||||
#[SearchWhere(method: SearchMethodEnum::Like)]
|
||||
public string $message;
|
||||
|
||||
#[SearchOrderBy(parameter: "created_at",)]
|
||||
public string $createdAt = SearchOrderBy::DESCENDING;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+12
-9
@@ -22,17 +22,17 @@ abstract class Save implements \Picea\Ui\Method\FormInterface {
|
||||
return $context->valid($this->getEntity()->isLoaded() ? $this->getEntity() : null);
|
||||
}
|
||||
|
||||
public function execute(FormContextInterface $context) : void
|
||||
public function execute(FormContextInterface $context) : mixed
|
||||
{
|
||||
$entity = $this->getEntity();
|
||||
|
||||
if ($entity->isLoaded() ) {
|
||||
if (property_exists($entity, 'updatedAt')) {
|
||||
if ($entity->isLoaded()) {
|
||||
if (property_exists($entity, 'updatedAt') && $entity->repository()->generateDatasetDiff($entity) ) {
|
||||
$entity->updatedAt = new Datetime();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (property_exists($entity, 'createdAt')) {
|
||||
if (property_exists($entity, 'createdAt') && empty($entity->createdAt)) {
|
||||
$entity->createdAt = new Datetime();
|
||||
}
|
||||
}
|
||||
@@ -40,18 +40,22 @@ abstract class Save implements \Picea\Ui\Method\FormInterface {
|
||||
try {
|
||||
$this->assignContextToEntity($context);
|
||||
|
||||
if ( $entity::repository()->save($entity) ) {
|
||||
if ( $saved = $entity::repository()->save($entity) ) {
|
||||
$context->pushMessage($this->message::generateSuccess(
|
||||
$this->lang('lean.api.form.save.success.entity')
|
||||
));
|
||||
}
|
||||
else {
|
||||
throw new \InvalidArgumentException($this->lang('lean.api.form.save.error.entity'));
|
||||
$context->pushMessage($this->message::generateWarning(
|
||||
$this->lang('lean.api.form.save.error.entity', [ 'entity' => $this->entity::class ])
|
||||
));
|
||||
}
|
||||
}
|
||||
catch(\Throwable $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() ]));
|
||||
}
|
||||
|
||||
return $saved;
|
||||
}
|
||||
|
||||
protected function assignContextToEntity(FormContextInterface $context) : void
|
||||
@@ -60,13 +64,12 @@ abstract class Save implements \Picea\Ui\Method\FormInterface {
|
||||
|
||||
foreach($entity::resolveEntity()->fieldList() as $key => $property) {
|
||||
$field = $property->getAttribute(Field::class)->object;
|
||||
|
||||
if (! $field->readonly || ! $entity->isLoaded()) {
|
||||
$apiField = $property->getAttribute(EntityField::class)->object ?? null;
|
||||
|
||||
if ($apiField) {
|
||||
$var = $apiField->field ?: $key;
|
||||
|
||||
|
||||
if ( isset($context->{$var}) ) {
|
||||
if ($apiField->setterMethod) {
|
||||
# Use a setter method
|
||||
|
||||
+21
-7
@@ -15,7 +15,6 @@ class FormDescriptor
|
||||
|
||||
foreach($forms as $form) {
|
||||
$fields = [];
|
||||
$propertyHtml = "";
|
||||
$formName = is_object($form) ? $form::class : $form;
|
||||
$reflector = new ObjectReflection($formName);
|
||||
|
||||
@@ -24,17 +23,19 @@ class FormDescriptor
|
||||
foreach($reflector->reflectProperties() as $property) {
|
||||
$field = $property->getAttribute(ContextField::class);
|
||||
|
||||
if ($field) {
|
||||
if ($field && ! $field->object->hidden ) {
|
||||
$types = $property->getTypes();
|
||||
|
||||
$fields[] = [
|
||||
'name' => $property->name,
|
||||
'description' => $field->object->description,
|
||||
'type' => $field->object->type ?? implode(' | ', array_map(fn($e) => $e->type, $types)),
|
||||
'type' => $field->object->type ?? $types,
|
||||
'allowNulls' => $property->allowsNull(),
|
||||
'regexPattern' =>$field->object->regexFormat ?? "aucun",
|
||||
'minLength' =>$field->object->minLength ?? "aucune",
|
||||
'maxLength' =>$field->object->maxLength ?? "aucune",
|
||||
'example' =>$field->object->example,
|
||||
'regexPattern' => $field->object->regexFormat ?? null,
|
||||
'minLength' => $field->object->minLength ?? null,
|
||||
'maxLength' => $field->object->maxLength ?? null,
|
||||
'example' => $field->object->example ?? null,
|
||||
'values' => $this->generateExampleValues($types),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -48,4 +49,17 @@ class FormDescriptor
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+88
-2
@@ -2,14 +2,24 @@
|
||||
|
||||
namespace Lean\Api;
|
||||
|
||||
use DI\Attribute\Inject;
|
||||
use League\CommonMark\CommonMarkConverter;
|
||||
use Lean\Api\Lib\ApiSearchRequestInterface;
|
||||
use Notes\Attribute\Ignore;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Ulmus\Entity\EntityInterface;
|
||||
use Ulmus\EntityCollection;
|
||||
use Ulmus\SearchRequest\SearchRequestInterface;
|
||||
|
||||
trait LeanApiTrait
|
||||
{
|
||||
use DescriptorTrait;
|
||||
|
||||
#[Inject]
|
||||
protected ContainerInterface $container;
|
||||
|
||||
#[Ignore]
|
||||
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}'))
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
if (str_contains($markdown, '{request:debugger}'))
|
||||
{
|
||||
$markdown = str_replace('{request:debugger}', $this->renderRawView('lean-api/request_debugger', []), $markdown);
|
||||
}
|
||||
|
||||
if (str_contains($markdown, '{entity:descriptor}'))
|
||||
{
|
||||
$markdown = str_replace('{entity:descriptor}', $this->renderRawView('lean-api/entity_descriptor', [
|
||||
@@ -44,10 +59,81 @@ trait LeanApiTrait
|
||||
}
|
||||
|
||||
#[Ignore]
|
||||
protected function output(\JsonSerializable|array $data, int $count) {
|
||||
protected function output(\JsonSerializable|array $data, int $count) : ResponseInterface
|
||||
{
|
||||
return $this->renderJson([
|
||||
'data' => $data,
|
||||
'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())));
|
||||
}
|
||||
|
||||
return $request->withAttribute("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;
|
||||
}
|
||||
}
|
||||
] + $request->getAttribute("lean.searchRequest", [])
|
||||
);
|
||||
}
|
||||
|
||||
#[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())));
|
||||
}
|
||||
|
||||
return $request->withAttribute("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;
|
||||
}
|
||||
}
|
||||
] + $request->getAttribute("lean.searchRequest", [])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
|
||||
namespace Lean\Api\Lib;
|
||||
|
||||
class FormContext extends \Picea\Ui\Method\FormContext { }
|
||||
@@ -4,29 +4,44 @@ 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 ApiRenderer implements MiddlewareInterface {
|
||||
|
||||
public function __construct(
|
||||
protected DebugFormFactoryInterface $debugFormFactory,
|
||||
) {
|
||||
}
|
||||
|
||||
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler) : ResponseInterface
|
||||
{
|
||||
try {
|
||||
try {
|
||||
$response = $handler->handle($request);
|
||||
}
|
||||
catch(\Exception $ex) {
|
||||
if ( ! getenv('DEBUG') ) {
|
||||
catch(\Throwable $ex) {
|
||||
$errorId = $this->saveError($request, $ex)->id;
|
||||
|
||||
if ($this->awaitingJson($request)) {
|
||||
return HttpFactory::createJsonResponse([
|
||||
'status' => 'failed',
|
||||
'message' => $ex->getMessage(),
|
||||
'ts' => time(),
|
||||
], 400);
|
||||
}
|
||||
else {
|
||||
throw $ex;
|
||||
'data' => [
|
||||
# 'error_id' => $errorId,
|
||||
'message' => $ex->getMessage(),
|
||||
'file' => $ex->getFile(),
|
||||
'line' => $ex->getLine(),
|
||||
'code' => $ex->getCode(),
|
||||
'backtrace' => $ex->getTrace(),
|
||||
]
|
||||
], 500);
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
if ($response instanceof JsonResponse) {
|
||||
@@ -43,4 +58,30 @@ class ApiRenderer implements MiddlewareInterface {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
protected function awaitingJson(ServerRequestInterface $request) : bool
|
||||
{
|
||||
return str_contains(strtolower($request->getHeaderLine('content-type')), 'json');
|
||||
}
|
||||
}
|
||||
+42
-8
@@ -5,22 +5,27 @@ namespace Lean\Api;
|
||||
use Lean\Factory\HttpFactory;
|
||||
use Notes\ObjectReflection;
|
||||
use Notes\Route\Attribute\Method\Route;
|
||||
use Notes\Security\SecurityHandler;
|
||||
use Picea\Extension\UrlExtension;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Taxus\Taxus;
|
||||
|
||||
class RouteDescriptor
|
||||
{
|
||||
public string $routeLine = <<<HTML
|
||||
<li>
|
||||
<span><a href='%s' title="%s" style='font-family:monospace;font-size:.85em'>%s</a> - %s</span>
|
||||
<span style='color:#ac1b1b'>%s</span>
|
||||
<small style="color:#374300">%s</small>
|
||||
</li>
|
||||
HTML;
|
||||
protected SecurityHandler $securityHandler;
|
||||
|
||||
protected Taxus $taxus;
|
||||
|
||||
public function __construct(
|
||||
public object $controller,
|
||||
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
|
||||
{
|
||||
@@ -47,6 +52,7 @@ HTML;
|
||||
'description'=> $route->description,
|
||||
#'methods' =>implode(', ', (array)$route->method),
|
||||
'methods' => (array) $route->method,
|
||||
'privileges' => $this->getPrivilegeFromRoute($method->name),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -66,4 +72,32 @@ HTML;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user