Merge DHCP and Actions branch
testing/ogcore-api/pipeline/head There was a failure building this commit Details

develop-jenkins
Manuel Aranda Rosales 2024-09-30 11:16:48 +02:00
commit 6be2832fe6
56 changed files with 2925 additions and 8 deletions

View File

@ -69,6 +69,7 @@ docker exec ogcore-php php bin/console doctrine:migrations:migrate --no-interact
```sh
docker exec ogcore-php php bin/console doctrine:fixtures:load --no-interaction
docker exec ogcore-php php bin/console app:load-default-user-groups #cargamos los grupos por defecto
docker exec ogcore-php php bin/console app:load-default-commands #cargamos los commands por defecto
```
## UX Api Platform
@ -133,8 +134,6 @@ docker exec ogcore-php php bin/console opengnsys:migration:hardware-profiles #ca
docker exec ogcore-php php bin/console opengnsys:migration:clients #cargamos los clientes
docker exec ogcore-php php bin/console opengnsys:migration:os #cargamos los sistemas operativos
docker exec ogcore-php php bin/console opengnsys:migration:partition #cargamos las particiones
```
## Objetos de interés

View File

@ -0,0 +1,39 @@
resources:
App\Entity\Command:
processor: App\State\Processor\CommandProcessor
input: App\Dto\Input\CommandInput
output: App\Dto\Output\CommandOutput
normalizationContext:
groups: ['default', 'command:read']
denormalizationContext:
groups: ['command:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\CommandProvider
filters:
- 'api_platform.filter.command.order'
- 'api_platform.filter.command.search'
- 'api_platform.filter.command.boolean'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\CommandProvider
ApiPlatform\Metadata\Put:
provider: App\State\Provider\CommandProvider
ApiPlatform\Metadata\Patch:
provider: App\State\Provider\CommandProvider
ApiPlatform\Metadata\Post: ~
ApiPlatform\Metadata\Delete: ~
command_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:
identifier: false
uuid:
identifier: true

View File

@ -0,0 +1,46 @@
resources:
App\Entity\CommandGroup:
processor: App\State\Processor\CommandGroupProcessor
input: App\Dto\Input\CommandGroupInput
output: App\Dto\Output\CommandGroupOutput
normalizationContext:
groups: ['default', 'command-group:read']
denormalizationContext:
groups: ['command-group:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\CommandGroupProvider
filters:
- 'api_platform.filter.command.order'
- 'api_platform.filter.command.search'
- 'api_platform.filter.command.boolean'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\CommandGroupProvider
ApiPlatform\Metadata\Put:
provider: App\State\Provider\CommandGroupProvider
ApiPlatform\Metadata\Patch:
provider: App\State\Provider\CommandGroupProvider
ApiPlatform\Metadata\Post: ~
ApiPlatform\Metadata\Delete: ~
add_commands:
class: ApiPlatform\Metadata\Post
method: POST
input: App\Dto\Input\CommandGroupAddCommandsInput
uriTemplate: /command-groups/{uuid}/add-commands
controller: App\Controller\CommandGroupAddCommandsAction
command_group_execute:
class: ApiPlatform\Metadata\Post
method: POST
input: App\Dto\Input\CommandGroupExecuteInput
uriTemplate: /command-groups/{uuid}/execute
controller: App\Controller\CommandGroupExecuteAction
properties:
App\Entity\CommandGroup:
id:
identifier: false
uuid:
identifier: true

View File

@ -0,0 +1,32 @@
resources:
App\Entity\CommandTask:
processor: App\State\Processor\CommandTaskProcessor
input: App\Dto\Input\CommandTaskInput
output: App\Dto\Output\CommandTaskOutput
normalizationContext:
groups: ['default', 'command-task:read']
denormalizationContext:
groups: ['command-task:write']
operations:
ApiPlatform\Metadata\GetCollection:
provider: App\State\Provider\CommandTaskProvider
filters:
- 'api_platform.filter.command_task.order'
- 'api_platform.filter.command_task.search'
- 'api_platform.filter.command_task.boolean'
ApiPlatform\Metadata\Get:
provider: App\State\Provider\CommandTaskProvider
ApiPlatform\Metadata\Put:
provider: App\State\Provider\CommandTaskProvider
ApiPlatform\Metadata\Patch:
provider: App\State\Provider\CommandTaskProvider
ApiPlatform\Metadata\Post: ~
ApiPlatform\Metadata\Delete: ~
properties:
App\Entity\CommandTask:
id:
identifier: false
uuid:
identifier: true

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

@ -40,4 +40,6 @@ api_platform:
api_keys:
apiKey:
name: Authorization
type: header
type: header
exception_to_status:
Doctrine\DBAL\Exception\ForeignKeyConstraintViolationException: 409

View File

@ -110,17 +110,36 @@ services:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\CommandProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\PxeTemplateProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\CommandGroupProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$itemProvider: '@api_platform.doctrine.orm.state.item_provider'
App\State\Provider\PxeBootFileProvider:
bind:
$collectionProvider: '@api_platform.doctrine.orm.state.collection_provider'
$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\SubnetProvider:
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

@ -8,7 +8,24 @@ services:
api_platform.filter.client.search:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { 'id': 'exact', 'name': 'partial', 'serialNumber': 'exact', organizationalUnit.id: 'exact' } ]
arguments: [ { 'id': 'exact', 'name': 'partial', 'serialNumber': 'exact', organizationalUnit.id: 'exact', mac: 'exact', ip: 'exact' } ]
tags: [ 'api_platform.filter' ]
api_platform.filter.command.order:
parent: 'api_platform.doctrine.orm.order_filter'
arguments:
$properties: { 'id': ~, 'name': ~ }
$orderParameterName: 'order'
tags: [ 'api_platform.filter' ]
api_platform.filter.command.search:
parent: 'api_platform.doctrine.orm.search_filter'
arguments: [ { 'id': 'exact', 'name': 'exact'} ]
tags: [ 'api_platform.filter' ]
api_platform.filter.command.boolean:
parent: 'api_platform.doctrine.orm.boolean_filter'
arguments: [ { 'enabled': ~ } ]
tags: [ 'api_platform.filter' ]
api_platform.filter.hardware.order:

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 Version20240916073039 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 command (id INT AUTO_INCREMENT 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, script VARCHAR(255) NOT NULL, comments VARCHAR(255) DEFAULT NULL, read_only TINYINT(1) NOT NULL, name VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_8ECAEAD4D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE command');
}
}

View File

@ -0,0 +1,37 @@
<?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 Version20240916091601 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 command_group (id INT AUTO_INCREMENT 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, position INT NOT NULL, name VARCHAR(255) NOT NULL, enabled TINYINT(1) NOT NULL, UNIQUE INDEX UNIQ_FE6811F6D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE command_group_command (command_group_id INT NOT NULL, command_id INT NOT NULL, INDEX IDX_118CE215C7B800D6 (command_group_id), INDEX IDX_118CE21533E1689A (command_id), PRIMARY KEY(command_group_id, command_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE command_group_command ADD CONSTRAINT FK_118CE215C7B800D6 FOREIGN KEY (command_group_id) REFERENCES command_group (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE command_group_command ADD CONSTRAINT FK_118CE21533E1689A FOREIGN KEY (command_id) REFERENCES command (id) ON DELETE CASCADE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE command_group_command DROP FOREIGN KEY FK_118CE215C7B800D6');
$this->addSql('ALTER TABLE command_group_command DROP FOREIGN KEY FK_118CE21533E1689A');
$this->addSql('DROP TABLE command_group');
$this->addSql('DROP TABLE command_group_command');
}
}

View File

@ -0,0 +1,43 @@
<?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 Version20240917064754 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 command_task (id INT AUTO_INCREMENT 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, datetime DATETIME NOT NULL, notes VARCHAR(255) DEFAULT NULL, status VARCHAR(255) NOT NULL, UNIQUE INDEX UNIQ_F3D475A8D17F50A6 (uuid), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE command_task_command (command_task_id INT NOT NULL, command_id INT NOT NULL, INDEX IDX_BB417CA862DC5265 (command_task_id), INDEX IDX_BB417CA833E1689A (command_id), PRIMARY KEY(command_task_id, command_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('CREATE TABLE command_task_command_group (command_task_id INT NOT NULL, command_group_id INT NOT NULL, INDEX IDX_C43618BD62DC5265 (command_task_id), INDEX IDX_C43618BDC7B800D6 (command_group_id), PRIMARY KEY(command_task_id, command_group_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
$this->addSql('ALTER TABLE command_task_command ADD CONSTRAINT FK_BB417CA862DC5265 FOREIGN KEY (command_task_id) REFERENCES command_task (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE command_task_command ADD CONSTRAINT FK_BB417CA833E1689A FOREIGN KEY (command_id) REFERENCES command (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE command_task_command_group ADD CONSTRAINT FK_C43618BD62DC5265 FOREIGN KEY (command_task_id) REFERENCES command_task (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE command_task_command_group ADD CONSTRAINT FK_C43618BDC7B800D6 FOREIGN KEY (command_group_id) REFERENCES command_group (id) ON DELETE CASCADE');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE command_task_command DROP FOREIGN KEY FK_BB417CA862DC5265');
$this->addSql('ALTER TABLE command_task_command DROP FOREIGN KEY FK_BB417CA833E1689A');
$this->addSql('ALTER TABLE command_task_command_group DROP FOREIGN KEY FK_C43618BD62DC5265');
$this->addSql('ALTER TABLE command_task_command_group DROP FOREIGN KEY FK_C43618BDC7B800D6');
$this->addSql('DROP TABLE command_task');
$this->addSql('DROP TABLE command_task_command');
$this->addSql('DROP TABLE command_task_command_group');
}
}

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,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 Version20240924071858 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 command CHANGE script script LONGTEXT NOT NULL');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE command CHANGE script script VARCHAR(255) NOT NULL');
}
}

View File

@ -0,0 +1,32 @@
<?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 Version20240926104532 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 command_group DROP position');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('ALTER TABLE organizational_unit ADD FK_749AEB2DC56641EE FOREIGN KEY (remote_calendar_id) REFERENCES remote_calendar (id)');
$this->addSql('ALTER TABLE command_group ADD position INT NOT NULL');
}
}

View File

@ -0,0 +1,94 @@
<?php
namespace App\Command;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
class LoadDefaultCommandsCommand extends Command
{
public function __construct(
private readonly EntityManagerInterface $entityManager,
)
{
parent::__construct('app:load-default-commands');
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$commands = [
[
'name' => 'Encender',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Apagar',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Restaurar Imagen',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Crear Imagen',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Reiniciar',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Inventario Hardware',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Inventario Software',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Ejecutar Script',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Iniciar Sesion',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Particionar y Formatear',
'enabled' => true,
'readOnly' => true,
],
[
'name' => 'Eliminar Imagen Cache',
'enabled' => true,
'readOnly' => true,
],
];
foreach ($commands as $command) {
$entity = new \App\Entity\Command();
$entity->setName($command['name']);
$entity->setScript('');
$entity->setEnabled($command['enabled']);
$entity->setReadOnly($command['readOnly']);
$this->entityManager->persist($entity);
}
$this->entityManager->flush();
return 1;
}
}

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,35 @@
<?php
namespace App\Controller;
use App\Dto\Input\CommandGroupAddCommandsInput;
use App\Entity\Command;
use App\Entity\CommandGroup;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Response;
class CommandGroupAddCommandsAction extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager
)
{
}
public function __invoke(CommandGroupAddCommandsInput $input, CommandGroup $commandGroup): JsonResponse
{
$commands = $input->commands;
/** @var Command $command */
foreach ($commands as $command) {
$commandGroup->addCommand($command->getEntity());
}
$this->entityManager->persist($commandGroup);
$this->entityManager->flush();
return new JsonResponse(data: 'Commands added to command group successfully', status: Response::HTTP_OK);
}
}

View File

@ -0,0 +1,46 @@
<?php
namespace App\Controller;
use App\Dto\Input\CommandGroupAddCommandsInput;
use App\Dto\Input\CommandGroupExecuteInput;
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 CommandGroupExecuteAction extends AbstractController
{
public function __construct(
private readonly EntityManagerInterface $entityManager
)
{
}
public function __invoke(CommandGroupExecuteInput $input, CommandGroup $commandGroup): JsonResponse
{
$clients = $input->clients;
foreach ($commandGroup->getCommands() as $command) {
/** @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 group 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

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

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 CommandGroupExecuteInput
{
/**
* @var ClientOutput[]
*/
#[Assert\NotNull]
#[Groups(['command-group:write'])]
public array $clients = [];
}

View File

@ -0,0 +1,73 @@
<?php
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Dto\Output\CommandOutput;
use App\Entity\Command;
use App\Entity\CommandGroup;
use phpDocumentor\Reflection\Types\Boolean;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
final class CommandGroupInput
{
#[Assert\NotBlank(message: 'validators.command_group.name.not_blank')]
#[Groups(['command-group:write'])]
#[ApiProperty(
description: 'El nombre del grupo de comandos',
example: 'Command Group 1',
)]
public ?string $name = null;
/**
* @var CommandOutput[]
*/
#[Groups(['command-group:write'])]
#[ApiProperty(
description: 'Los comandos del grupo',
example: 'Comandos del grupo',
)]
public array $commands = [];
#[Groups(['command-group:write'])]
#[ApiProperty(
description: '¿Está habilitado el grupo de comandos?',
example: 'true',
)]
public ?bool $enabled = true;
public function __construct(?CommandGroup $commandGroup = null)
{
if (!$commandGroup) {
return;
}
$this->name = $commandGroup->getName();
if ($commandGroup->getCommands()) {
foreach ($commandGroup->getCommands() as $command) {
$this->commands[] = new CommandOutput($command);
}
}
$this->enabled = $commandGroup->isEnabled();
}
public function createOrUpdateEntity(?CommandGroup $commandGroup = null): CommandGroup
{
if (!$commandGroup) {
$commandGroup = new CommandGroup();
}
$commandGroup->setName($this->name);
foreach ($this->commands as $command) {
$commandsToAdd[] = $command->getEntity();
}
$commandGroup->setCommands( $commandsToAdd ?? [] );
return $commandGroup;
}
}

View File

@ -0,0 +1,77 @@
<?php
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Entity\Command;
use phpDocumentor\Reflection\Types\Boolean;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
final class CommandInput
{
#[Assert\NotBlank(message: 'validators.command.name.not_blank')]
#[Groups(['command:write'])]
#[ApiProperty(
description: 'El nombre del comando',
example: 'Command 1',
)]
public ?string $name = null;
#[Assert\NotBlank(message: 'validators.command.script.not_blank')]
#[Groups(['command:write'])]
#[ApiProperty(
description: 'El script del comando',
example: 'echo "Hello World"',
)]
public ?string $script = null;
#[Groups(['command:write'])]
#[ApiProperty(
description: 'Es interno el comando?',
example: 'true',
)]
public ?bool $readOnly = false;
#[Groups(['command:write'])]
#[ApiProperty(
description: 'Esta activo?',
example: 'true',
)]
public ?bool $enabled = true;
#[Groups(['command:write'])]
#[ApiProperty(
description: 'Los comentarios del comando',
example: 'Comentarios del comando',
)]
public ?string $comments = null;
public function __construct(?Command $command = null)
{
if (!$command) {
return;
}
$this->name = $command->getName();
$this->script = $command->getScript();
$this->enabled = $command->isEnabled();
$this->readOnly = $command->isReadOnly();
$this->comments = $command->getComments();
}
public function createOrUpdateEntity(?Command $command = null): Command
{
if (!$command) {
$command = new Command();
}
$command->setName($this->name);
$command->setScript($this->script);
$command->setEnabled($this->enabled);
$command->setReadOnly($this->readOnly);
$command->setComments($this->comments);
return $command;
}
}

View File

@ -0,0 +1,120 @@
<?php
namespace App\Dto\Input;
use ApiPlatform\Metadata\ApiProperty;
use App\Dto\Output\ClientOutput;
use App\Dto\Output\CommandGroupOutput;
use App\Dto\Output\CommandOutput;
use App\Entity\CommandTask;
use App\Model\CommandTaskStatus;
use phpDocumentor\Reflection\Types\Boolean;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Validator\Constraints as Assert;
final class CommandTaskInput
{
/**
* @var CommandOutput[]
*/
#[Groups(['command-task:write'])]
#[ApiProperty(
description: 'Los comandos de la tarea',
example: 'Comandos de la tarea',
)]
public array $commands = [];
/**
* @var CommandGroupOutput[]
*/
#[Groups(['command-task:write'])]
#[ApiProperty(
description: 'Los grupos de comandos de la tarea',
example: 'Grupos de comandos de la tarea',
)]
public array $commandGroups = [];
/**
* @var ClientOutput[]
*/
#[Groups(['command-task:write'])]
#[ApiProperty(
description: 'Los clientes de la tarea',
example: 'Clientes de la tarea',
)]
public array $clients = [];
#[Assert\NotBlank(message: 'validators.command_task.datetime.not_blank')]
#[Groups(['command-task:write'])]
#[ApiProperty(
description: 'La fecha y hora de la tarea',
example: '2021-10-01T00:00:00+00:00',
)]
public ?\DateTimeInterface $dateTime = null;
#[Groups(['command-task:write'])]
#[ApiProperty(
description: 'Los comentarios de la tarea',
example: 'Comentarios de la tarea',
)]
public ?string $notes = null;
public function __construct(?CommandTask $commandTask = null)
{
if (!$commandTask) {
return;
}
if ($commandTask->getCommands()) {
foreach ($commandTask->getCommands() as $command) {
$this->commands[] = new CommandOutput($command);
}
}
if ($commandTask->getCommandGroups()) {
foreach ($commandTask->getCommandGroups() as $commandGroup) {
$this->commandGroups[] = new CommandGroupOutput($commandGroup);
}
}
if ($commandTask->getClients()) {
foreach ($commandTask->getClients() as $client) {
$this->clients[] = new ClientOutput($client);
}
}
$this->dateTime = $commandTask->getDatetime();
$this->notes = $commandTask->getNotes();
}
public function createOrUpdateEntity(?CommandTask $commandTask = null): CommandTask
{
if (!$commandTask) {
$commandTask = new CommandTask();
}
foreach ($this->commands as $command) {
$commandsToAdd[] = $command->getEntity();
}
$commandTask->setCommands( $commandsToAdd ?? [] );
foreach ($this->commandGroups as $commandGroup) {
$commandGroupsToAdd[] = $commandGroup->getEntity();
}
$commandTask->setCommandGroups( $commandGroupsToAdd ?? [] );
foreach ($this->clients as $client) {
$clientsToAdd[] = $client->getEntity();
}
$commandTask->setClients( $clientsToAdd ?? [] );
$commandTask->setDatetime($this->dateTime);
$commandTask->setStatus(CommandTaskStatus::PENDING);
$commandTask->setNotes($this->notes);
return $commandTask;
}
}

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

@ -0,0 +1,38 @@
<?php
namespace App\Dto\Output;
use ApiPlatform\Metadata\Get;
use App\Entity\Command;
use App\Entity\CommandGroup;
use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'CommandGroup')]
final class CommandGroupOutput extends AbstractOutput
{
#[Groups(['command-group:read', 'command-task:read'])]
public string $name;
#[Groups(['command-group:read', 'command-task:read'])]
public array $commands = [];
#[Groups(['command-group:read'])]
public \DateTime $createdAt;
#[Groups(['command-group:read'])]
public ?string $createdBy = null;
public function __construct(CommandGroup $commandGroup)
{
parent::__construct($commandGroup);
$this->name = $commandGroup->getName();
$this->commands = $commandGroup->getCommands()->map(
fn(Command $command) => new CommandOutput($command)
)->toArray();
$this->createdAt = $commandGroup->getCreatedAt();
$this->createdBy = $commandGroup->getCreatedBy();
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Dto\Output;
use ApiPlatform\Metadata\Get;
use App\Entity\Command;
use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'Command')]
final class CommandOutput extends AbstractOutput
{
#[Groups(['command:read', 'command-group:read', 'command-task:read', 'trace:read'])]
public string $name;
#[Groups(['command:read', 'command-group:read', 'command-task:read', 'trace:read'])]
public ?string $script = '';
#[Groups(['command:read'])]
public ?bool $readOnly = false;
#[Groups(['command:read'])]
public ?bool $enabled = true;
#[Groups(['command:read'])]
public ?string $comments = '';
#[Groups(['command:read'])]
public \DateTime $createdAt;
#[Groups(['command:read'])]
public ?string $createdBy = null;
public function __construct(Command $command)
{
parent::__construct($command);
$this->name = $command->getName();
$this->script = $command->getScript();
$this->readOnly = $command->isReadOnly();
$this->enabled = $command->isEnabled();
$this->comments = $command->getComments();
$this->createdAt = $command->getCreatedAt();
$this->createdBy = $command->getCreatedBy();
}
}

View File

@ -0,0 +1,62 @@
<?php
namespace App\Dto\Output;
use ApiPlatform\Metadata\Get;
use App\Entity\Client;
use App\Entity\Command;
use App\Entity\CommandGroup;
use App\Entity\CommandTask;
use Symfony\Component\Serializer\Annotation\Groups;
#[Get(shortName: 'CommandTask')]
final class CommandTaskOutput extends AbstractOutput
{
#[Groups(['command-task:read'])]
public array $commands = [];
#[Groups(['command-task:read'])]
public array $commandGroups = [];
#[Groups(['command-task:read'])]
public array $clients = [];
#[Groups(['command-task:read'])]
public \DateTimeInterface $dateTime;
#[Groups(['command-task:read'])]
public ?string $notes = null;
#[Groups(['command-task:read'])]
public ?string $status = null;
#[Groups(['command-task:read'])]
public \DateTime $createdAt;
#[Groups(['command-task:read'])]
public ?string $createdBy = null;
public function __construct(CommandTask $commandTask)
{
parent::__construct($commandTask);
$this->commands = $commandTask->getCommands()->map(
fn(Command $command) => new CommandOutput($command)
)->toArray();
$this->commandGroups = $commandTask->getCommandGroups()->map(
fn(CommandGroup $commandGroup) => new CommandGroupOutput($commandGroup)
)->toArray();
$this->clients = $commandTask->getClients()->map(
fn(Client $client) => new ClientOutput($client)
)->toArray();
$this->dateTime = $commandTask->getDateTime();
$this->notes = $commandTask->getNotes();
$this->status = $commandTask->getStatus();
$this->createdAt = $commandTask->getCreatedAt();
$this->createdBy = $commandTask->getCreatedBy();
}
}

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

@ -0,0 +1,134 @@
<?php
namespace App\Entity;
use App\Repository\CommandRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CommandRepository::class)]
class Command extends AbstractEntity
{
use NameableTrait;
use ToggleableTrait;
#[ORM\Column(type: Types::TEXT)]
private ?string $script = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $comments = null;
#[ORM\Column]
private ?bool $readOnly = null;
/**
* @var Collection<int, CommandGroup>
*/
#[ORM\ManyToMany(targetEntity: CommandGroup::class, mappedBy: 'commands')]
private Collection $commandGroups;
/**
* @var Collection<int, CommandTask>
*/
#[ORM\ManyToMany(targetEntity: CommandTask::class, mappedBy: 'commands')]
private Collection $commandTasks;
public function __construct()
{
parent::__construct();
$this->commandGroups = new ArrayCollection();
$this->commandTasks = new ArrayCollection();
}
public function getScript(): ?string
{
return $this->script;
}
public function setScript(string $script): static
{
$this->script = $script;
return $this;
}
public function getComments(): ?string
{
return $this->comments;
}
public function setComments(?string $comments): static
{
$this->comments = $comments;
return $this;
}
public function isReadOnly(): ?bool
{
return $this->readOnly;
}
public function setReadOnly(bool $readOnly): static
{
$this->readOnly = $readOnly;
return $this;
}
/**
* @return Collection<int, CommandGroup>
*/
public function getCommandGroups(): Collection
{
return $this->commandGroups;
}
public function addCommandGroup(CommandGroup $commandGroup): static
{
if (!$this->commandGroups->contains($commandGroup)) {
$this->commandGroups->add($commandGroup);
$commandGroup->addCommand($this);
}
return $this;
}
public function removeCommandGroup(CommandGroup $commandGroup): static
{
if ($this->commandGroups->removeElement($commandGroup)) {
$commandGroup->removeCommand($this);
}
return $this;
}
/**
* @return Collection<int, CommandTask>
*/
public function getCommandTasks(): Collection
{
return $this->commandTasks;
}
public function addCommandTask(CommandTask $commandTask): static
{
if (!$this->commandTasks->contains($commandTask)) {
$this->commandTasks->add($commandTask);
$commandTask->addCommand($this);
}
return $this;
}
public function removeCommandTask(CommandTask $commandTask): static
{
if ($this->commandTasks->removeElement($commandTask)) {
$commandTask->removeCommand($this);
}
return $this;
}
}

View File

@ -0,0 +1,96 @@
<?php
namespace App\Entity;
use App\Repository\CommandGroupRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CommandGroupRepository::class)]
class CommandGroup extends AbstractEntity
{
use NameableTrait;
use ToggleableTrait;
/**
* @var Collection<int, Command>
*/
#[ORM\ManyToMany(targetEntity: Command::class, inversedBy: 'commandGroups')]
private Collection $commands;
/**
* @var Collection<int, CommandTask>
*/
#[ORM\ManyToMany(targetEntity: CommandTask::class, mappedBy: 'commandGroups')]
private Collection $commandTasks;
public function __construct()
{
parent::__construct();
$this->commands = new ArrayCollection();
$this->commandTasks = new ArrayCollection();
}
/**
* @return Collection<int, Command>
*/
public function getCommands(): Collection
{
return $this->commands;
}
public function setCommands(array $commands): static
{
$this->commands->clear();
foreach ($commands as $command){
$this->addCommand($command);
}
return $this;
}
public function addCommand(Command $command): static
{
if (!$this->commands->contains($command)) {
$this->commands->add($command);
}
return $this;
}
public function removeCommand(Command $command): static
{
$this->commands->removeElement($command);
return $this;
}
/**
* @return Collection<int, CommandTask>
*/
public function getCommandTasks(): Collection
{
return $this->commandTasks;
}
public function addCommandTask(CommandTask $commandTask): static
{
if (!$this->commandTasks->contains($commandTask)) {
$this->commandTasks->add($commandTask);
$commandTask->addCommandGroup($this);
}
return $this;
}
public function removeCommandTask(CommandTask $commandTask): static
{
if ($this->commandTasks->removeElement($commandTask)) {
$commandTask->removeCommandGroup($this);
}
return $this;
}
}

View File

@ -0,0 +1,190 @@
<?php
namespace App\Entity;
use App\Repository\CommandTaskRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: CommandTaskRepository::class)]
class CommandTask extends AbstractEntity
{
/**
* @var Collection<int, Command>
*/
#[ORM\ManyToMany(targetEntity: Command::class, inversedBy: 'commandTasks')]
private Collection $commands;
/**
* @var Collection<int, CommandGroup>
*/
#[ORM\ManyToMany(targetEntity: CommandGroup::class, inversedBy: 'commandTasks')]
private Collection $commandGroups;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $datetime = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $notes = null;
#[ORM\Column(length: 255)]
private ?string $status = null;
/**
* @var Collection<int, Client>
*/
#[ORM\ManyToMany(targetEntity: Client::class)]
private Collection $clients;
public function __construct()
{
parent::__construct();
$this->commands = new ArrayCollection();
$this->commandGroups = new ArrayCollection();
$this->clients = new ArrayCollection();
}
/**
* @return Collection<int, Command>
*/
public function getCommands(): Collection
{
return $this->commands;
}
public function setCommands(array $commands): static
{
$this->commands->clear();
foreach ($commands as $command){
$this->addCommand($command);
}
return $this;
}
public function addCommand(Command $command): static
{
if (!$this->commands->contains($command)) {
$this->commands->add($command);
}
return $this;
}
public function removeCommand(Command $command): static
{
$this->commands->removeElement($command);
return $this;
}
/**
* @return Collection<int, CommandGroup>
*/
public function getCommandGroups(): Collection
{
return $this->commandGroups;
}
public function setCommandGroups(array $commandGroups): static
{
$this->commandGroups->clear();
foreach ($commandGroups as $commandGroup){
$this->addCommandGroup($commandGroup);
}
return $this;
}
public function addCommandGroup(CommandGroup $commandGroup): static
{
if (!$this->commandGroups->contains($commandGroup)) {
$this->commandGroups->add($commandGroup);
}
return $this;
}
public function removeCommandGroup(CommandGroup $commandGroup): static
{
$this->commandGroups->removeElement($commandGroup);
return $this;
}
public function getDatetime(): ?\DateTimeInterface
{
return $this->datetime;
}
public function setDatetime(\DateTimeInterface $datetime): static
{
$this->datetime = $datetime;
return $this;
}
public function getNotes(): ?string
{
return $this->notes;
}
public function setNotes(?string $notes): static
{
$this->notes = $notes;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): static
{
$this->status = $status;
return $this;
}
/**
* @return Collection<int, Client>
*/
public function getClients(): Collection
{
return $this->clients;
}
public function addClient(Client $client): static
{
if (!$this->clients->contains($client)) {
$this->clients->add($client);
}
return $this;
}
public function removeClient(Client $client): static
{
$this->clients->removeElement($client);
return $this;
}
public function setClients(array $clients): static
{
$this->clients->clear();
foreach ($clients as $client){
$this->addClient($client);
}
return $this;
}
}

View File

@ -0,0 +1,103 @@
<?php
namespace App\Entity;
use App\Repository\TraceRepository;
use Doctrine\DBAL\Types\Types;
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity(repositoryClass: TraceRepository::class)]
class Trace extends AbstractEntity
{
#[ORM\ManyToOne(inversedBy: 'traces')]
#[ORM\JoinColumn(nullable: false)]
private ?Client $client = null;
#[ORM\ManyToOne(inversedBy: 'traces')]
#[ORM\JoinColumn(nullable: false)]
private ?Command $command = null;
#[ORM\Column(length: 255)]
private ?string $status = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $output = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE)]
private ?\DateTimeInterface $executedAt = null;
#[ORM\Column(type: Types::DATETIME_MUTABLE, nullable: true)]
private ?\DateTimeInterface $finishedAt = null;
public function getClient(): ?Client
{
return $this->client;
}
public function setClient(?Client $client): static
{
$this->client = $client;
return $this;
}
public function getCommand(): ?Command
{
return $this->command;
}
public function setCommand(?Command $command): static
{
$this->command = $command;
return $this;
}
public function getStatus(): ?string
{
return $this->status;
}
public function setStatus(string $status): static
{
$this->status = $status;
return $this;
}
public function getOutput(): ?string
{
return $this->output;
}
public function setOutput(?string $output): static
{
$this->output = $output;
return $this;
}
public function getExecutedAt(): ?\DateTimeInterface
{
return $this->executedAt;
}
public function setExecutedAt(\DateTimeInterface $executedAt): static
{
$this->executedAt = $executedAt;
return $this;
}
public function getFinishedAt(): ?\DateTimeInterface
{
return $this->finishedAt;
}
public function setFinishedAt(\DateTimeInterface $finishedAt): static
{
$this->finishedAt = $finishedAt;
return $this;
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace App\Factory;
use App\Entity\Command;
use App\Repository\CommandRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator;
/**
* @extends PersistentProxyObjectFactory<Command>
*/
final class CommandFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'createdAt' => self::faker()->dateTime(),
'enabled' => self::faker()->boolean(),
'name' => self::faker()->text(255),
'readOnly' => self::faker()->boolean(),
'script' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime()
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(Command $command): void {})
;
}
public static function getClass(): string
{
return Command::class;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Factory;
use App\Entity\CommandGroup;
use App\Repository\CommandGroupRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator;
/**
* @extends PersistentProxyObjectFactory<CommandGroup>
*/
final class CommandGroupFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
public static function getClass(): string
{
return CommandGroup::class;
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'createdAt' => self::faker()->dateTime(),
'enabled' => self::faker()->boolean(),
'name' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime()
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(CommandGroup $commandGroup): void {})
;
}
}

View File

@ -0,0 +1,56 @@
<?php
namespace App\Factory;
use App\Entity\CommandTask;
use App\Repository\CommandTaskRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator;
/**
* @extends PersistentProxyObjectFactory<CommandTask>
*/
final class CommandTaskFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
public static function getClass(): string
{
return CommandTask::class;
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'createdAt' => self::faker()->dateTime(),
'datetime' => self::faker()->dateTime(),
'status' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime()
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): self
{
return $this
// ->afterInstantiate(function(CommandTask $commandTask): void {})
;
}
}

View File

@ -0,0 +1,58 @@
<?php
namespace App\Factory;
use App\Entity\Trace;
use App\Repository\TraceRepository;
use Zenstruck\Foundry\ModelFactory;
use Zenstruck\Foundry\Persistence\PersistentProxyObjectFactory;
use Zenstruck\Foundry\Persistence\Proxy;
use Zenstruck\Foundry\Persistence\ProxyRepositoryDecorator;
/**
* @extends PersistentProxyObjectFactory<Trace>
*/
final class TraceFactory extends ModelFactory
{
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#factories-as-services
*
* @todo inject services if required
*/
public function __construct()
{
parent::__construct();
}
public static function getClass(): string
{
return Trace::class;
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#model-factories
*
* @todo add your default values here
*/
protected function getDefaults(): array
{
return [
'client' => ClientFactory::new(),
'command' => CommandFactory::new(),
'createdAt' => self::faker()->dateTime(),
'executedAt' => self::faker()->dateTime(),
'status' => self::faker()->text(255),
'updatedAt' => self::faker()->dateTime()
];
}
/**
* @see https://symfony.com/bundles/ZenstruckFoundryBundle/current/index.html#initialization
*/
protected function initialize(): static
{
return $this
// ->afterInstantiate(function(Trace $trace): void {})
;
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace App\Model;
final class CommandTaskStatus
{
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 getCommandTaskStatus(string $status): ?string
{
return self::STATUS[$status] ?? null;
}
public static function getStatusKeys(): array
{
return array_keys(self::STATUS);
}
}

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,18 @@
<?php
namespace App\Repository;
use App\Entity\CommandGroup;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<CommandGroup>
*/
class CommandGroupRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CommandGroup::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\Command;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Command>
*/
class CommandRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Command::class);
}
}

View File

@ -0,0 +1,18 @@
<?php
namespace App\Repository;
use App\Entity\CommandTask;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<CommandTask>
*/
class CommandTaskRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, CommandTask::class);
}
}

View File

@ -0,0 +1,19 @@
<?php
namespace App\Repository;
use App\Entity\Trace;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
/**
* @extends ServiceEntityRepository<Trace>
*/
class TraceRepository extends AbstractRepository
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, Trace::class);
}
}

View File

@ -0,0 +1,69 @@
<?php
namespace App\State\Processor;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\Dto\Input\CommandGroupInput;
use App\Dto\Output\CommandGroupOutput;
use App\Repository\CommandGroupRepository;
use App\Repository\CommandRepository;
readonly class CommandGroupProcessor implements ProcessorInterface
{
public function __construct(
private CommandGroupRepository $commandGroupRepository,
private ValidatorInterface $validator
)
{
}
/**
* @throws \Exception
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): CommandGroupOutput|null
{
switch ($operation){
case $operation instanceof Post:
case $operation instanceof Put:
case $operation instanceof Patch:
return $this->processCreateOrUpdate($data, $operation, $uriVariables, $context);
case $operation instanceof Delete:
return $this->processDelete($data, $operation, $uriVariables, $context);
}
}
/**
* @throws \Exception
*/
private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): CommandGroupOutput
{
if (!($data instanceof CommandGroupInput)) {
throw new \Exception(sprintf('data is not instance of %s', CommandGroupInput::class));
}
$entity = null;
if (isset($uriVariables['uuid'])) {
$entity = $this->commandGroupRepository->findOneByUuid($uriVariables['uuid']);
}
$command = $data->createOrUpdateEntity($entity);
$this->validator->validate($command);
$this->commandGroupRepository->save($command);
return new CommandGroupOutput($command);
}
private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null
{
$user = $this->commandGroupRepository->findOneByUuid($uriVariables['uuid']);
$this->commandGroupRepository->delete($user);
return null;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\State\Processor;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\Dto\Input\CommandInput;
use App\Dto\Output\CommandOutput;
use App\Repository\CommandRepository;
readonly class CommandProcessor implements ProcessorInterface
{
public function __construct(
private CommandRepository $commandRepository,
private ValidatorInterface $validator
)
{
}
/**
* @throws \Exception
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): CommandOutput|null
{
switch ($operation){
case $operation instanceof Post:
case $operation instanceof Put:
case $operation instanceof Patch:
return $this->processCreateOrUpdate($data, $operation, $uriVariables, $context);
case $operation instanceof Delete:
return $this->processDelete($data, $operation, $uriVariables, $context);
}
}
/**
* @throws \Exception
*/
private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): CommandOutput
{
if (!($data instanceof CommandInput)) {
throw new \Exception(sprintf('data is not instance of %s', CommandInput::class));
}
$entity = null;
if (isset($uriVariables['uuid'])) {
$entity = $this->commandRepository->findOneByUuid($uriVariables['uuid']);
}
$command = $data->createOrUpdateEntity($entity);
$this->validator->validate($command);
$this->commandRepository->save($command);
return new CommandOutput($command);
}
private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null
{
$user = $this->commandRepository->findOneByUuid($uriVariables['uuid']);
$this->commandRepository->delete($user);
return null;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\State\Processor;
use ApiPlatform\Metadata\Delete;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Patch;
use ApiPlatform\Metadata\Post;
use ApiPlatform\Metadata\Put;
use ApiPlatform\State\ProcessorInterface;
use ApiPlatform\Validator\ValidatorInterface;
use App\Dto\Input\CommandTaskInput;
use App\Dto\Output\CommandTaskOutput;
use App\Repository\CommandTaskRepository;
readonly class CommandTaskProcessor implements ProcessorInterface
{
public function __construct(
private CommandTaskRepository $commandTaskRepository,
private ValidatorInterface $validator
)
{
}
/**
* @throws \Exception
*/
public function process(mixed $data, Operation $operation, array $uriVariables = [], array $context = []): CommandTaskOutput|null
{
switch ($operation){
case $operation instanceof Post:
case $operation instanceof Put:
case $operation instanceof Patch:
return $this->processCreateOrUpdate($data, $operation, $uriVariables, $context);
case $operation instanceof Delete:
return $this->processDelete($data, $operation, $uriVariables, $context);
}
}
/**
* @throws \Exception
*/
private function processCreateOrUpdate($data, Operation $operation, array $uriVariables = [], array $context = []): CommandTaskOutput
{
if (!($data instanceof CommandTaskInput)) {
throw new \Exception(sprintf('data is not instance of %s', CommandTaskInput::class));
}
$entity = null;
if (isset($uriVariables['uuid'])) {
$entity = $this->commandTaskRepository->findOneByUuid($uriVariables['uuid']);
}
$command = $data->createOrUpdateEntity($entity);
$this->validator->validate($command);
$this->commandTaskRepository->save($command);
return new CommandTaskOutput($command);
}
private function processDelete($data, Operation $operation, array $uriVariables = [], array $context = []): null
{
$user = $this->commandTaskRepository->findOneByUuid($uriVariables['uuid']);
$this->commandTaskRepository->delete($user);
return null;
}
}

View File

@ -0,0 +1,72 @@
<?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\Input\CommandGroupInput;
use App\Dto\Input\CommandInput;
use App\Dto\Output\CommandGroupOutput;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class CommandGroupProvider 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 Patch:
case $operation instanceof Put:
return $this->provideInput($operation, $uriVariables, $context);
case $operation instanceof Get:
return $this->provideItem($operation, $uriVariables, $context);
}
}
private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$paginator = $this->collectionProvider->provide($operation, $uriVariables, $context);
$items = new \ArrayObject();
foreach ($paginator->getIterator() as $item){
$items[] = new CommandGroupOutput($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('Command group not found');
}
return new CommandGroupOutput($item);
}
public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['uuid'])) {
$item = $this->itemProvider->provide($operation, $uriVariables, $context);
return $item !== null ? new CommandGroupInput($item) : null;
}
return new CommandGroupInput();
}
}

View File

@ -0,0 +1,71 @@
<?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\Input\CommandInput;
use App\Dto\Output\CommandOutput;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class CommandProvider 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 Patch:
case $operation instanceof Put:
return $this->provideInput($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 CommandOutput($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('Command not found');
}
return new CommandOutput($item);
}
public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['uuid'])) {
$item = $this->itemProvider->provide($operation, $uriVariables, $context);
return $item !== null ? new CommandInput($item) : null;
}
return new CommandInput();
}
}

View File

@ -0,0 +1,71 @@
<?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\Input\CommandTaskInput;
use App\Dto\Output\CommandTaskOutput;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
readonly class CommandTaskProvider 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 Patch:
case $operation instanceof Put:
return $this->provideInput($operation, $uriVariables, $context);
case $operation instanceof Get:
return $this->provideItem($operation, $uriVariables, $context);
}
}
private function provideCollection(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
$paginator = $this->collectionProvider->provide($operation, $uriVariables, $context);
$items = new \ArrayObject();
foreach ($paginator->getIterator() as $item){
$items[] = new CommandTaskOutput($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('Command task not found');
}
return new CommandTaskOutput($item);
}
public function provideInput(Operation $operation, array $uriVariables = [], array $context = []): object|array|null
{
if (isset($uriVariables['uuid'])) {
$item = $this->itemProvider->provide($operation, $uriVariables, $context);
return $item !== null ? new CommandTaskInput($item) : null;
}
return new CommandTaskInput();
}
}

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);
}
}

View File

@ -0,0 +1,127 @@
<?php
namespace Functional;
use App\Entity\Command;
use App\Entity\CommandGroup;
use App\Factory\CommandFactory;
use App\Factory\CommandGroupFactory;
use App\Factory\UserFactory;
use App\Model\UserGroupPermissions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class CommandGroupTest extends AbstractTest
{
CONST string USER_ADMIN = 'ogadmin';
CONST string CMD_GROUP_CREATE = 'test-cmd-group-create';
CONST string CMD_GROUP_UPDATE = 'test-cmd-group-update';
CONST string CMD_GROUP_DELETE = 'test-cmd-group-delete';
CONST string CMD_CREATE = 'test-cmd-create';
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testGetCollectionCommandGroup(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandGroupFactory::createMany(10);
$this->createClientWithCredentials()->request('GET', '/command-groups');
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/CommandGroup',
'@id' => '/command-groups',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 10,
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testCreateCommandGroup(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandFactory::createOne(['name' => self::CMD_CREATE]);
$commandIri = $this->findIriBy(Command::class, ['name' => self::CMD_CREATE]);
$this->createClientWithCredentials()->request('POST', '/command-groups',['json' => [
'name' => self::CMD_GROUP_CREATE,
'commands' => [
$commandIri
]
]]);
$this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/CommandGroupOutput',
'@type' => 'CommandGroup',
'name' => self::CMD_GROUP_CREATE,
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testUpdateCommandGroup(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandGroupFactory::createOne(['name' => self::CMD_GROUP_CREATE]);
$iri = $this->findIriBy(CommandGroup::class, ['name' => self::CMD_GROUP_CREATE]);
$this->createClientWithCredentials()->request('PUT', $iri, ['json' => [
'name' => self::CMD_GROUP_UPDATE,
]]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@id' => $iri,
'name' => self::CMD_GROUP_UPDATE
]);
}
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
*/
public function testDeleteCommandGroup(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandGroupFactory::createOne(['name' => self::CMD_GROUP_DELETE]);
$iri = $this->findIriBy(CommandGroup::class, ['name' => self::CMD_GROUP_DELETE]);
$this->createClientWithCredentials()->request('DELETE', $iri);
$this->assertResponseStatusCodeSame(204);
$this->assertNull(
static::getContainer()->get('doctrine')->getRepository(CommandGroup::class)->findOneBy(['name' => self::CMD_GROUP_DELETE])
);
}
}

View File

@ -0,0 +1,129 @@
<?php
namespace Functional;
use App\Entity\Command;
use App\Entity\CommandTask;
use App\Factory\CommandFactory;
use App\Factory\CommandTaskFactory;
use App\Factory\UserFactory;
use App\Model\UserGroupPermissions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class CommandTaskTest extends AbstractTest
{
CONST string USER_ADMIN = 'ogadmin';
CONST string CMD_TASK_CREATE = 'test-cmd-task-create';
CONST string CMD_TASK_UPDATE = 'test-cmd-task-update';
CONST string CMD_TASK_DELETE = 'test-cmd-task-delete';
CONST string CMD_CREATE = 'test-cmd-create';
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testGetCollectionCommandTask(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandTaskFactory::createMany(10);
$this->createClientWithCredentials()->request('GET', '/command-tasks');
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/CommandTask',
'@id' => '/command-tasks',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 10,
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testCreateCommandTask(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandFactory::createOne(['name' => self::CMD_CREATE]);
$commandIri = $this->findIriBy(Command::class, ['name' => self::CMD_CREATE]);
$date = new \DateTimeImmutable();
$this->createClientWithCredentials()->request('POST', '/command-tasks',['json' => [
'dateTime' => $date->format('Y-m-d H:i:s'),
'notes' => self::CMD_TASK_CREATE,
'commands' => [
$commandIri
]
]]);
$this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/CommandTaskOutput',
'@type' => 'CommandTask',
'notes' => self::CMD_TASK_CREATE,
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testUpdateCommandTask(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandTaskFactory::createOne(['notes' => self::CMD_TASK_CREATE]);
$iri = $this->findIriBy(CommandTask::class, ['notes' => self::CMD_TASK_CREATE]);
$this->createClientWithCredentials()->request('PUT', $iri, ['json' => [
'notes' => self::CMD_TASK_UPDATE,
]]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@id' => $iri,
'notes' => self::CMD_TASK_UPDATE
]);
}
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
*/
public function testDeleteCommandTask(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandTaskFactory::createOne(['notes' => self::CMD_TASK_DELETE]);
$iri = $this->findIriBy(CommandTask::class, ['notes' => self::CMD_TASK_DELETE]);
$this->createClientWithCredentials()->request('DELETE', $iri);
$this->assertResponseStatusCodeSame(204);
$this->assertNull(
static::getContainer()->get('doctrine')->getRepository(CommandTask::class)->findOneBy(['notes' => self::CMD_TASK_DELETE])
);
}
}

View File

@ -0,0 +1,122 @@
<?php
namespace Functional;
use App\Entity\Command;
use App\Factory\CommandFactory;
use App\Factory\UserFactory;
use App\Model\UserGroupPermissions;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
class CommandTest extends AbstractTest
{
CONST string USER_ADMIN = 'ogadmin';
CONST string CMD_CREATE = 'test-cmd-create';
CONST string CMD_UPDATE = 'test-cmd-update';
CONST string CMD_DELETE = 'test-cmd-delete';
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testGetCollectionCommand(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandFactory::createMany(10);
$this->createClientWithCredentials()->request('GET', '/commands');
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/Command',
'@id' => '/commands',
'@type' => 'hydra:Collection',
'hydra:totalItems' => 10,
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testCreateCommand(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
$this->createClientWithCredentials()->request('POST', '/commands',['json' => [
'name' => self::CMD_CREATE,
'script' => 'echo "Hello World!"',
]]);
$this->assertResponseStatusCodeSame(201);
$this->assertResponseHeaderSame('content-type', 'application/ld+json; charset=utf-8');
$this->assertJsonContains([
'@context' => '/contexts/CommandOutput',
'@type' => 'Command',
'name' => self::CMD_CREATE,
'script' => 'echo "Hello World!"'
]);
}
/**
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
*/
public function testUpdateCommand(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandFactory::createOne(['name' => self::CMD_CREATE]);
$iri = $this->findIriBy(Command::class, ['name' => self::CMD_CREATE]);
$this->createClientWithCredentials()->request('PUT', $iri, ['json' => [
'name' => self::CMD_UPDATE,
'readOnly' => true
]]);
$this->assertResponseIsSuccessful();
$this->assertJsonContains([
'@id' => $iri,
'name' => self::CMD_UPDATE,
'readOnly' => true
]);
}
/**
* @throws TransportExceptionInterface
* @throws ServerExceptionInterface
* @throws RedirectionExceptionInterface
* @throws DecodingExceptionInterface
* @throws ClientExceptionInterface
*/
public function testDeleteCommand(): void
{
UserFactory::createOne(['username' => self::USER_ADMIN, 'roles'=> [UserGroupPermissions::ROLE_SUPER_ADMIN]]);
CommandFactory::createOne(['name' => self::CMD_DELETE]);
$iri = $this->findIriBy(Command::class, ['name' => self::CMD_DELETE]);
$this->createClientWithCredentials()->request('DELETE', $iri);
$this->assertResponseStatusCodeSame(204);
$this->assertNull(
static::getContainer()->get('doctrine')->getRepository(Command::class)->findOneBy(['name' => self::CMD_DELETE])
);
}
}

View File

@ -8,6 +8,18 @@ validators:
organizational_unit:
not_null: 'The organizational unit should not be null.'
command:
name:
not_blank: 'The name should not be blank.'
script:
not_blank: 'The script should not be blank.'
command_group:
name:
not_blank: 'The name should not be blank.'
position:
not_blank: 'The position should not be blank.'
view:
name:
not_blank: 'The name should not be blank.'

View File

@ -8,6 +8,18 @@ validators:
organizational_unit:
not_null: 'La unidad organizativa no debería estar vacía.'
command:
name:
not_blank: 'El nombre no debería estar vacío.'
script:
not_blank: 'El script no debería estar vacío.'
command_group:
name:
not_blank: 'El nombre no debería estar vacío.'
position:
not_blank: 'La posición no debería estar vacía.'
view:
name:
not_blank: 'El nombre no debería estar vacío.'