refs #659. Trace API. New endpoint execute in command

feature/actions
Manuel Aranda Rosales 2024-09-17 11:28:02 +02:00
parent b4f9f1e9bb
commit d1105a1e89
13 changed files with 311 additions and 13 deletions

View File

@ -24,6 +24,13 @@ resources:
ApiPlatform\Metadata\Post: ~
ApiPlatform\Metadata\Delete: ~
execute:
class: ApiPlatform\Metadata\Post
method: POST
input: App\Dto\Input\CommandExecuteInput
uriTemplate: /commands/{uuid}/execute
controller: App\Controller\CommandExecuteAction
properties:
App\Entity\Command:
id:

View File

@ -0,0 +1,20 @@
resources:
App\Entity\Trace:
output: App\Dto\Output\TraceOutput
normalizationContext:
groups: ['default', 'trace:read']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\TraceProvider
filters:
- 'api_platform.filter.trace.order'
- 'api_platform.filter.trace.search'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\TraceProvider
properties:
App\Entity\Trace:
id:
identifier: false
uuid:
identifier: true

View File

@ -113,6 +113,11 @@ services:
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\CommandTaskProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\TraceProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240917091950 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE trace (id INT AUTO_INCREMENT NOT NULL, client_id INT NOT NULL, command_id INT NOT NULL, uuid CHAR(36) NOT NULL COMMENT \'(DC2Type:uuid)\', migration_id VARCHAR(255) DEFAULT NULL, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, created_by VARCHAR(255) DEFAULT NULL, updated_by VARCHAR(255) DEFAULT NULL, status VARCHAR(255) NOT NULL, output VARCHAR(255) DEFAULT NULL, executed_at DATETIME NOT NULL, finished_at DATETIME NOT NULL, UNIQUE INDEX UNIQ_315BD5A1D17F50A6 (uuid), INDEX IDX_315BD5A119EB6921 (client_id), INDEX IDX_315BD5A133E1689A (command_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE trace ADD CONSTRAINT FK_315BD5A119EB6921 FOREIGN KEY (client_id) REFERENCES client (id)');
$this->addSql('ALTER TABLE trace ADD CONSTRAINT FK_315BD5A133E1689A FOREIGN KEY (command_id) REFERENCES command (id)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE trace DROP FOREIGN KEY FK_315BD5A119EB6921');
$this->addSql('ALTER TABLE trace DROP FOREIGN KEY FK_315BD5A133E1689A');
$this->addSql('DROP TABLE trace');
}
}

View File

@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20240917092207 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE trace CHANGE finished_at finished_at DATETIME DEFAULT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE trace CHANGE finished_at finished_at DATETIME NOT NULL');
}
}

View File

@ -0,0 +1,44 @@
<?php
namespace App\Controller;
use App\Dto\Input\CommandExecuteInput;
use App\Dto\Input\CommandGroupAddCommandsInput;
use App\Entity\Client;
use App\Entity\Command;
use App\Entity\CommandGroup;
use App\Entity\Trace;
use App\Model\TraceStatus;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class CommandExecuteAction extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager
)
{
}
public function __invoke(CommandExecuteInput $input, Command $command): JsonResponse
{
$clients = $input->clients;
/** @var Client $client */
foreach ($clients as $client) {
$trace = new Trace();
$trace->setClient($client->getEntity());
$trace->setCommand($command);
$trace->setStatus(TraceStatus::IN_PROGRESS);
$trace->setExecutedAt(new \DateTimeImmutable());
$this->entityManager->persist($trace);
}
$this->entityManager->flush();
return new JsonResponse(data: 'Command executed successfully', status: Response::HTTP_OK);
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Dto\Input;
use App\Dto\Output\ClientOutput;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
final class CommandExecuteInput
{
/**
* @var ClientOutput[]
*/
#[Assert\NotNull]
#[Groups(['command:write'])]
public array $clients = [];
}

View File

@ -13,19 +13,19 @@ final class ClientOutput extends AbstractOutput
{
CONST string TYPE = 'client';
#[Groups(['client:read', 'organizational-unit:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'trace:read'])]
public string $name;
#[Groups(['client:read', 'organizational-unit:read'])]
public string $type = self::TYPE;
#[Groups(['client:read', 'organizational-unit:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'trace:read'])]
public ?string $ip = '';
#[Groups(['client:read', 'organizational-unit:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'trace:read'])]
public ?string $mac = '';
#[Groups(['client:read', 'organizational-unit:read'])]
#[Groups(['client:read', 'organizational-unit:read', 'trace:read'])]
public ?string $serialNumber = '';
#[Groups(['client:read'])]

View File

@ -9,10 +9,10 @@ use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'Command')]
final class CommandOutput extends AbstractOutput
{
#[Groups(['command:read', 'command-group:read', 'command-task:read'])]
#[Groups(['command:read', 'command-group:read', 'command-task:read', 'trace:read'])]
public string $name;
#[Groups(['command:read', 'command-group:read', 'command-task:read'])]
#[Groups(['command:read', 'command-group:read', 'command-task:read', 'trace:read'])]
public ?string $script = '';
#[Groups(['command:read'])]

View File

@ -0,0 +1,50 @@
<?php
namespace App\Dto\Output;
use ApiPlatform\Metadata\Get;
use App\Entity\Menu;
use App\Entity\Trace;
use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'Trace')]
final class TraceOutput extends AbstractOutput
{
#[Groups(['trace:read'])]
public CommandOutput $command;
#[Groups(['trace:read'])]
public ClientOutput $client;
#[Groups(['trace:read'])]
public string $status;
#[Groups(['trace:read'])]
public ?\DateTimeInterface $executedAt = null;
#[Groups(['trace:read'])]
public ?string $output = null;
#[Groups(['trace:read'])]
public ?\DateTimeInterface $finishedAt = null;
#[Groups(['trace:read'])]
public \DateTime $createdAt;
#[Groups(['trace:read'])]
public ?string $createdBy = null;
public function __construct(Trace $trace)
{
parent::__construct($trace);
$this->command = new CommandOutput($trace->getCommand());
$this->client = new ClientOutput($trace->getClient());
$this->status = $trace->getStatus();
$this->executedAt = $trace->getExecutedAt();
$this->output = $trace->getOutput();
$this->finishedAt = $trace->getFinishedAt();
$this->createdAt = $trace->getCreatedAt();
$this->createdBy = $trace->getCreatedBy();
}
}

View File

@ -24,10 +24,10 @@ class Trace extends AbstractEntity
private ?string $output = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeImmutable $executedAt = null;
private ?\DateTimeInterface $executedAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeImmutable $finishedAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $finishedAt = null;
public function getClient(): ?Client
{
@ -77,24 +77,24 @@ class Trace extends AbstractEntity
return $this;
}
public function getExecutedAt(): ?\DateTimeImmutable
public function getExecutedAt(): ?\DateTimeInterface
{
return $this->executedAt;
}
public function setExecutedAt(\DateTimeImmutable $executedAt): static
public function setExecutedAt(\DateTimeInterface $executedAt): static
{
$this->executedAt = $executedAt;
return $this;
}
public function getFinishedAt(): ?\DateTimeImmutable
public function getFinishedAt(): ?\DateTimeInterface
{
return $this->finishedAt;
}
public function setFinishedAt(\DateTimeImmutable $finishedAt): static
public function setFinishedAt(\DateTimeInterface $finishedAt): static
{
$this->finishedAt = $finishedAt;

View File

@ -0,0 +1,33 @@
<?php
namespace App\Model;
final class TraceStatus
{
public const string PENDING = 'pending';
public const string IN_PROGRESS = 'in-progress';
public const string COMPLETED = 'completed';
public const string FAILED = 'failed';
private const array STATUS = [
self::PENDING => 'Pendiente',
self::IN_PROGRESS => 'En progreso',
self::COMPLETED => 'Completado',
self::FAILED => 'Fallido',
];
public static function getStatus(): array
{
return self::STATUS;
}
public static function getTraceStatus(string $status): ?string
{
return self::STATUS[$status] ?? null;
}
public static function getStatusKeys(): array
{
return array_keys(self::STATUS);
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\State\Provider;
use ApiPlatform\Metadata\Get;
use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\Pagination\TraversablePaginator;
use ApiPlatform\State\ProviderInterface;
use App\Dto\Output\TraceOutput;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class TraceProvider implements ProviderInterface
{
public function __construct(
private ProviderInterface $collectionProvider,
private ProviderInterface $itemProvider
)
{
}
public function provide(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
switch ($operation){
case $operation instanceof GetCollection:
return $this->provideCollection($operation, $uriVariables, $context);
case $operation instanceof Get:
return $this->provideItem($operation, $uriVariables, $context);
}
}
private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object
{
$paginator = $this->collectionProvider->provide($operation, $uriVariables, $context);
$items = new \ArrayObject();
foreach ($paginator->getIterator() as $item){
$items[] = new TraceOutput($item);
}
return new TraversablePaginator($items, $paginator->getCurrentPage(), $paginator->getItemsPerPage(), $paginator->getTotalItems());
}
public function provideItem(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$item = $this->itemProvider->provide($operation, $uriVariables, $context);
if (!$item) {
throw new NotFoundHttpException('Trace not found');
}
return new TraceOutput($item);
}
}