From f012507a8c05d82c508d13d00068e3ad39ca2fd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 14:36:00 +0200 Subject: [PATCH 1/6] Api: endpoints to list devices & owners --- schema/mydb.sql | 15 ++ src/app/Core/Devices/Device.php | 20 ++ src/app/Core/Devices/OperatingSystem.php | 15 ++ src/app/Core/Devices/Repository.php | 55 +++++ src/app/Core/Devices/Type.php | 13 ++ src/app/Core/Owners/Owner.php | 17 ++ src/app/Core/Owners/Repository.php | 43 ++++ src/app/Core/RouterFactory.php | 8 + .../Api/Devices/DevicesPresenter.php | 53 +++++ .../Api/Owners/OwnersPresenter.php | 47 ++++ src/composer.json | 1 + src/composer.lock | 216 +++++++++++++++++- 12 files changed, 502 insertions(+), 1 deletion(-) create mode 100644 schema/mydb.sql create mode 100644 src/app/Core/Devices/Device.php create mode 100644 src/app/Core/Devices/OperatingSystem.php create mode 100644 src/app/Core/Devices/Repository.php create mode 100644 src/app/Core/Devices/Type.php create mode 100644 src/app/Core/Owners/Owner.php create mode 100644 src/app/Core/Owners/Repository.php create mode 100644 src/app/Presentation/Api/Devices/DevicesPresenter.php create mode 100644 src/app/Presentation/Api/Owners/OwnersPresenter.php diff --git a/schema/mydb.sql b/schema/mydb.sql new file mode 100644 index 0000000..590e8fd --- /dev/null +++ b/schema/mydb.sql @@ -0,0 +1,15 @@ +CREATE TABLE `devices` ( + `uuid` varchar(36) NOT NULL, + `owner_uuid` varchar(36) NOT NULL, + `hostname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, + `operating_system` ENUM('android', 'iOS', 'lin', 'macOS', 'win') NOT NULL, + `type` ENUM('laptop', 'mobile', 'pc') NOT NULL, + PRIMARY KEY (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE `owners` ( + `uuid` varchar(36) NOT NULL, + `firstname` varchar(50) NOT NULL, + `lastname` varchar(50) NOT NULL, + PRIMARY KEY (`uuid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/src/app/Core/Devices/Device.php b/src/app/Core/Devices/Device.php new file mode 100644 index 0000000..db16042 --- /dev/null +++ b/src/app/Core/Devices/Device.php @@ -0,0 +1,20 @@ + + */ + public function loadDevices(): array + { + return array_map( + fn (Dibi\Row $row) => $this->createDevice($row), + $this->database->query(' + SELECT + `devices`.*, + `owners`.`uuid` AS `owner_uuid`, + `owners`.`firstname` AS `owner_firstname`, + `owners`.`lastname` AS `owner_lastname` + FROM `devices` + LEFT JOIN `owners` ON `devices`.`owner_uuid` = `owners`.`uuid` + ')->fetchAll(), + ); + } + + + + private function createDevice(Dibi\Row $row): Device + { + return new Device( + uuid: Ramsey\Uuid\Uuid::fromString($row->uuid), + hostname: $row->hostname, + operatingSystem: OperatingSystem::from($row->operating_system), + owner: new WbAssignment\Core\Owners\Owner( + uuid: Ramsey\Uuid\Uuid::fromString($row->owner_uuid), + firstname: $row->owner_firstname, + lastname: $row->owner_lastname, + ), + type: Type::from($row->type), + ); + } + +} diff --git a/src/app/Core/Devices/Type.php b/src/app/Core/Devices/Type.php new file mode 100644 index 0000000..3b2650d --- /dev/null +++ b/src/app/Core/Devices/Type.php @@ -0,0 +1,13 @@ + + */ + public function loadOwners(): array + { + return array_map( + fn (Dibi\Row $row) => $this->createOwner($row), + $this->database->query(' + SELECT * + FROM `owners` + ')->fetchAll(), + ); + } + + + + private function createOwner(Dibi\Row $row): Owner + { + return new Owner( + uuid: Ramsey\Uuid\Uuid::fromString($row->uuid), + firstname: $row->firstname, + lastname: $row->lastname, + ); + } + +} diff --git a/src/app/Core/RouterFactory.php b/src/app/Core/RouterFactory.php index 834d843..0edc3be 100644 --- a/src/app/Core/RouterFactory.php +++ b/src/app/Core/RouterFactory.php @@ -15,7 +15,15 @@ final class RouterFactory public static function createRouter(): Nette\Application\Routers\RouteList { $router = new Nette\Application\Routers\RouteList(); + + $router->addRoute('api/[/]', [ + 'module' => 'Api', + 'presenter' => 'Home', + 'action' => 'default', + ]); + $router->addRoute('/[/]', 'Home:default'); + return $router; } diff --git a/src/app/Presentation/Api/Devices/DevicesPresenter.php b/src/app/Presentation/Api/Devices/DevicesPresenter.php new file mode 100644 index 0000000..f4edc63 --- /dev/null +++ b/src/app/Presentation/Api/Devices/DevicesPresenter.php @@ -0,0 +1,53 @@ +method === 'GET') { + return new Nette\Application\Responses\JsonResponse( + array_map( + [$this, 'formatDevice'], + $this->devicesRepository->loadDevices(), + ), + ); + } + + throw new Nette\NotImplementedException(); + } + + + + private function formatDevice(WbAssignment\Core\Devices\Device $device): array + { + return [ + 'uuid' => $device->uuid, + 'hostname' => $device->hostname, + 'operating_system' => $device->operatingSystem, + 'owner' => [ + 'uuid' => $device->owner->uuid, + 'firstname' => $device->owner->firstname, + 'lastname' => $device->owner->lastname, + ], + 'type' => $device->type, + ]; + } + +} diff --git a/src/app/Presentation/Api/Owners/OwnersPresenter.php b/src/app/Presentation/Api/Owners/OwnersPresenter.php new file mode 100644 index 0000000..1730d3e --- /dev/null +++ b/src/app/Presentation/Api/Owners/OwnersPresenter.php @@ -0,0 +1,47 @@ +method === 'GET') { + return new Nette\Application\Responses\JsonResponse( + array_map( + [$this, 'formatOwner'], + $this->ownersRepository->loadOwners(), + ), + ); + } + + throw new Nette\NotImplementedException(); + } + + + + private function formatOwner(WbAssignment\Core\Owners\Owner $owner): array + { + return [ + 'uuid' => $owner->uuid, + 'firstname' => $owner->firstname, + 'lastname' => $owner->lastname, + ]; + } + +} diff --git a/src/composer.json b/src/composer.json index 35c65c0..405fb4e 100644 --- a/src/composer.json +++ b/src/composer.json @@ -19,6 +19,7 @@ "nette/robot-loader": "^4.0", "nette/security": "^3.2", "nette/utils": "^4.0", + "ramsey/uuid": "^4.9", "tracy/tracy": "^2.10" }, "require-dev": { diff --git a/src/composer.lock b/src/composer.lock index 54444a8..f222f28 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,8 +4,68 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "7907fc5cc6bbb75089392d951175aaf4", + "content-hash": "098409b27b4e7e8f6c2b8d35935c5421", "packages": [ + { + "name": "brick/math", + "version": "0.13.1", + "source": { + "type": "git", + "url": "https://github.com/brick/math.git", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/brick/math/zipball/fc7ed316430118cc7836bf45faff18d5dfc8de04", + "reference": "fc7ed316430118cc7836bf45faff18d5dfc8de04", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.2", + "phpunit/phpunit": "^10.1", + "vimeo/psalm": "6.8.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "Brick\\Math\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Arbitrary-precision arithmetic library", + "keywords": [ + "Arbitrary-precision", + "BigInteger", + "BigRational", + "arithmetic", + "bigdecimal", + "bignum", + "bignumber", + "brick", + "decimal", + "integer", + "math", + "mathematics", + "rational" + ], + "support": { + "issues": "https://github.com/brick/math/issues", + "source": "https://github.com/brick/math/tree/0.13.1" + }, + "funding": [ + { + "url": "https://github.com/BenMorel", + "type": "github" + } + ], + "time": "2025-03-29T13:50:30+00:00" + }, { "name": "dibi/dibi", "version": "v5.1.0", @@ -1435,6 +1495,160 @@ }, "time": "2025-08-06T21:43:34+00:00" }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { + "rhumsaa/uuid": "self.version" + }, + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.0" + }, + "time": "2025-06-25T14:20:11+00:00" + }, { "name": "tracy/tracy", "version": "v2.10.10", From 8cae5540b52f537972c09a115b3c75c2abd6ebdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 14:55:00 +0200 Subject: [PATCH 2/6] Api: allow creating devices & owners --- src/app/Core/Devices/Creator.php | 48 ++++++++++ .../Exceptions/OperationFailedException.php | 11 +++ src/app/Core/Owners/Creator.php | 43 +++++++++ .../Owners/Exceptions/NotFoundException.php | 11 +++ .../Exceptions/OperationFailedException.php | 11 +++ src/app/Core/Owners/Repository.php | 17 ++++ src/app/Presentation/Api/BaseApiPresenter.php | 29 +++++++ .../Api/Devices/DevicesPresenter.php | 87 ++++++++++++++++++- .../Api/Owners/OwnersPresenter.php | 68 ++++++++++++++- src/config/services.neon | 1 + 10 files changed, 323 insertions(+), 3 deletions(-) create mode 100644 src/app/Core/Devices/Creator.php create mode 100644 src/app/Core/Devices/Exceptions/OperationFailedException.php create mode 100644 src/app/Core/Owners/Creator.php create mode 100644 src/app/Core/Owners/Exceptions/NotFoundException.php create mode 100644 src/app/Core/Owners/Exceptions/OperationFailedException.php create mode 100644 src/app/Presentation/Api/BaseApiPresenter.php diff --git a/src/app/Core/Devices/Creator.php b/src/app/Core/Devices/Creator.php new file mode 100644 index 0000000..e83f4ad --- /dev/null +++ b/src/app/Core/Devices/Creator.php @@ -0,0 +1,48 @@ +database->query(' + INSERT INTO `devices`', [ + 'uuid%s' => $uuid->toString(), + 'owner_uuid%s' => $owner->uuid->toString(), + 'hostname%s' => $hostname, + 'operating_system%s' => $operatingSystem, + 'type%s' => $type, + ], + ); + } catch (Dibi\ConstraintViolationException) { + throw new Exceptions\OperationFailedException(); + } + + return $uuid; + } + +} diff --git a/src/app/Core/Devices/Exceptions/OperationFailedException.php b/src/app/Core/Devices/Exceptions/OperationFailedException.php new file mode 100644 index 0000000..0dc34ab --- /dev/null +++ b/src/app/Core/Devices/Exceptions/OperationFailedException.php @@ -0,0 +1,11 @@ +database->query(' + INSERT INTO `owners`', [ + 'uuid%s' => $uuid->toString(), + 'firstname%s' => $firstname, + 'lastname%s' => $lastname, + ], + ); + } catch (Dibi\ConstraintViolationException) { + throw new Exceptions\OperationFailedException(); + } + + return $uuid; + } + +} diff --git a/src/app/Core/Owners/Exceptions/NotFoundException.php b/src/app/Core/Owners/Exceptions/NotFoundException.php new file mode 100644 index 0000000..cbceedb --- /dev/null +++ b/src/app/Core/Owners/Exceptions/NotFoundException.php @@ -0,0 +1,11 @@ +database->query(' + SELECT * + FROM `owners` + WHERE `uuid` = %s', $uuid->toString(), ' + ')->fetch(); + + if ($row === NULL) { + throw new Exceptions\NotFoundException(); + } + + return $this->createOwner($row); + } + + + /** * @return list */ diff --git a/src/app/Presentation/Api/BaseApiPresenter.php b/src/app/Presentation/Api/BaseApiPresenter.php new file mode 100644 index 0000000..0321789 --- /dev/null +++ b/src/app/Presentation/Api/BaseApiPresenter.php @@ -0,0 +1,29 @@ +|NULL + */ + protected function getRequestData(): ?array + { + $jsonData = $this->getHttpRequest()->getRawBody(); + + if ($jsonData === NULL) { + return NULL; + } + + try { + return Nette\Utils\Json::decode($jsonData, TRUE); + } catch (Nette\Utils\JsonException) { + return NULL; + } + } + +} diff --git a/src/app/Presentation/Api/Devices/DevicesPresenter.php b/src/app/Presentation/Api/Devices/DevicesPresenter.php index f4edc63..6bf11ae 100644 --- a/src/app/Presentation/Api/Devices/DevicesPresenter.php +++ b/src/app/Presentation/Api/Devices/DevicesPresenter.php @@ -3,14 +3,17 @@ namespace WbAssignment\Presentation\Api\Devices; use Nette; +use Ramsey; use WbAssignment; -final class DevicesPresenter extends Nette\Application\UI\Presenter +final class DevicesPresenter extends WbAssignment\Presentation\Api\BaseApiPresenter { public function __construct( + private readonly WbAssignment\Core\Devices\Creator $devicesCreator, private readonly WbAssignment\Core\Devices\Repository $devicesRepository, + private readonly WbAssignment\Core\Owners\Repository $ownersRepository, ) { parent::__construct(); @@ -28,6 +31,54 @@ public function run(Nette\Application\Request $request): Nette\Application\Respo $this->devicesRepository->loadDevices(), ), ); + } elseif ($request->method === 'POST') { + $input = $this->getRequestData(); + + if ($input === NULL) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S400_BadRequest); + return new Nette\Application\Responses\JsonResponse([ + 'error' => 'Invalid or missing request input', + ]); + } + + try { + $data = $this->validateAndNormalizeInput($input); + } catch ( + Nette\Schema\ValidationException + | Ramsey\Uuid\Exception\InvalidUuidStringException $e + ) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S400_BadRequest); + return new Nette\Application\Responses\JsonResponse([ + 'error' => $e->getMessage(), + ]); + } + + try { + $owner = $this->ownersRepository->getOwner($data['owner_uuid']); + } catch (WbAssignment\Core\Owners\Exceptions\NotFoundException) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S400_BadRequest); + return new Nette\Application\Responses\JsonResponse([ + 'error' => sprintf("Owner '%s' does not exist", $data['owner_uuid']->toString()), + ]); + } + + try { + $uuid = $this->devicesCreator->create( + hostname: $data['hostname'], + operatingSystem: $data['operating_system'], + owner: $owner, + type: $data['type'], + ); + } catch (WbAssignment\Core\Devices\Exceptions\OperationFailedException) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S500_InternalServerError); + return new Nette\Application\Responses\JsonResponse([ + 'error' => 'Some error has occurred', + ]); + } + + return new Nette\Application\Responses\JsonResponse([ + 'uuid' => $uuid, + ]); } throw new Nette\NotImplementedException(); @@ -50,4 +101,38 @@ private function formatDevice(WbAssignment\Core\Devices\Device $device): array ]; } + + + /** + * @param array $input + * @return array{ + * hostname: string, + * operating_system: WbAssignment\Core\Devices\OperatingSystem, + * owner_uuid: Ramsey\Uuid\UuidInterface, + * type: WbAssignment\Core\Devices\Type, + * } + */ + private function validateAndNormalizeInput(array $input): array + { + $schemaProcessor = new Nette\Schema\Processor(); + + return $schemaProcessor->process( + (new Nette\Schema\Elements\Structure([ + 'hostname' => Nette\Schema\Expect::string() + ->max(255) + ->required(), + 'operating_system' => Nette\Schema\Expect::anyOf(...WbAssignment\Core\Devices\OperatingSystem::cases()) + ->before(static fn ($operatingSystem) => WbAssignment\Core\Devices\OperatingSystem::tryFrom($operatingSystem)) + ->required(), + 'owner_uuid' => Nette\Schema\Expect::type(Ramsey\Uuid\UuidInterface::class) + ->before(static fn ($ownerUuid) => Ramsey\Uuid\Uuid::fromString($ownerUuid)) + ->required(), + 'type' => Nette\Schema\Expect::anyOf(...WbAssignment\Core\Devices\Type::cases()) + ->before(static fn ($type) => WbAssignment\Core\Devices\Type::tryFrom($type)) + ->required(), + ]))->castTo('array'), + $input, + ); + } + } diff --git a/src/app/Presentation/Api/Owners/OwnersPresenter.php b/src/app/Presentation/Api/Owners/OwnersPresenter.php index 1730d3e..2ce09d8 100644 --- a/src/app/Presentation/Api/Owners/OwnersPresenter.php +++ b/src/app/Presentation/Api/Owners/OwnersPresenter.php @@ -6,10 +6,11 @@ use WbAssignment; -final class OwnersPresenter extends Nette\Application\UI\Presenter +final class OwnersPresenter extends WbAssignment\Presentation\Api\BaseApiPresenter { public function __construct( + private readonly WbAssignment\Core\Owners\Creator $ownersCreator, private readonly WbAssignment\Core\Owners\Repository $ownersRepository, ) { @@ -28,9 +29,46 @@ public function run(Nette\Application\Request $request): Nette\Application\Respo $this->ownersRepository->loadOwners(), ), ); + } elseif ($request->method === 'POST') { + $requestData = $this->getRequestData(); + + if ($requestData === NULL) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S400_BadRequest); + return new Nette\Application\Responses\JsonResponse([ + 'error' => 'Invalid or missing request input', + ]); + } + + try { + $data = $this->validateAndNormalizeInput($requestData); + } catch (Nette\Schema\ValidationException $e) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S400_BadRequest); + return new Nette\Application\Responses\JsonResponse([ + 'error' => $e->getMessage(), + ]); + } + + try { + $uuid = $this->ownersCreator->create( + $data['firstname'], + $data['lastname'], + ); + } catch (WbAssignment\Core\Owners\Exceptions\OperationFailedException) { + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S500_InternalServerError); + return new Nette\Application\Responses\JsonResponse([ + 'error' => 'Some error has occurred', + ]); + } + + return new Nette\Application\Responses\JsonResponse([ + 'uuid' => $uuid, + ]); } - throw new Nette\NotImplementedException(); + $this->getHttpResponse()->setCode(Nette\Http\IResponse::S405_MethodNotAllowed); + return new Nette\Application\Responses\JsonResponse([ + 'error' => sprintf('Method %s is allowed', $request->method), + ]); } @@ -44,4 +82,30 @@ private function formatOwner(WbAssignment\Core\Owners\Owner $owner): array ]; } + + + /** + * @param array $input + * @return array{ + * firstname: string, + * lastname: string, + * } + */ + private function validateAndNormalizeInput(array $input): array + { + $schemaProcessor = new Nette\Schema\Processor(); + + return $schemaProcessor->process( + (new Nette\Schema\Elements\Structure([ + 'firstname' => Nette\Schema\Expect::string() + ->max(50) + ->required(), + 'lastname' => Nette\Schema\Expect::string() + ->max(50) + ->required(), + ]))->castTo('array'), + $input, + ); + } + } diff --git a/src/config/services.neon b/src/config/services.neon index 2e183df..b04bdf7 100644 --- a/src/config/services.neon +++ b/src/config/services.neon @@ -5,6 +5,7 @@ services: search: - in: %appDir% classes: + - *Creator - *Facade - *Factory - *Repository From c44a1aaba74be32136a5ce0e5b9ce3e01a9e646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 15:15:00 +0200 Subject: [PATCH 3/6] Tests: few test cases --- .github/workflows/tests.yml | 3 + Makefile | 4 + phpstan.neon | 4 + src/composer.json | 8 +- src/composer.lock | 319 +++++++++++++++++- tester-bootstrap.php | 6 + tester-databasePreparation.php | 28 ++ tests/app/Core/Owners/Creator.phpt | 36 ++ .../app/Core/Owners/Repository.getOwner.phpt | 48 +++ .../Core/Owners/Repository.listOwners.phpt | 41 +++ .../app/Core/Owners/dataProviders/Creator.php | 16 + 11 files changed, 509 insertions(+), 4 deletions(-) create mode 100644 tester-bootstrap.php create mode 100644 tester-databasePreparation.php create mode 100644 tests/app/Core/Owners/Creator.phpt create mode 100644 tests/app/Core/Owners/Repository.getOwner.phpt create mode 100644 tests/app/Core/Owners/Repository.listOwners.phpt create mode 100644 tests/app/Core/Owners/dataProviders/Creator.php diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4db8b93..d91446f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -31,3 +31,6 @@ jobs: - name: Run phpstan run: make phpstan + + - name: Run tester + run: make tester diff --git a/Makefile b/Makefile index fe220ae..988f966 100644 --- a/Makefile +++ b/Makefile @@ -17,3 +17,7 @@ dockerComposeUp: .PHONE phpstan: phpstan: cd ./src && composer phpstan + +.PHONE tester: +tester: + cd ./src && composer tester diff --git a/phpstan.neon b/phpstan.neon index 7b5adbf..1b404a0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -13,11 +13,15 @@ parameters: - php - phpt + ignoreErrors: + - '#Class dibi referenced with incorrect case: Dibi\.#' + level: 5 paths: - ./src/app - ./src/www + - ./tests reportAnyTypeWideningInVarTag: true diff --git a/src/composer.json b/src/composer.json index 405fb4e..75e347f 100644 --- a/src/composer.json +++ b/src/composer.json @@ -23,6 +23,8 @@ "tracy/tracy": "^2.10" }, "require-dev": { + "fakerphp/faker": "^1.24", + "mockery/mockery": "^1.6", "nette/tester": "^2.5", "phpstan/phpstan-deprecation-rules": "^2.0", "phpstan/phpstan-dibi": "^2.0", @@ -39,11 +41,11 @@ } }, "scripts": { - "codesniffer": "vendor/bin/phpcs --standard=../phpcs.xml --extensions=php,phpt --colors -p -s ./app", - "codesnifferFix": "vendor/bin/phpcbf --standard=../phpcs.xml --extensions=php,phpt --colors -p -s ./app", + "codesniffer": "vendor/bin/phpcs --standard=../phpcs.xml --extensions=php,phpt --colors -p -s ./app ../tests", + "codesnifferFix": "vendor/bin/phpcbf --standard=../phpcs.xml --extensions=php,phpt --colors -p -s ./app ../tests", "phpstan": "phpstan analyse -c ../phpstan.neon", "server": "cd ./www && php -S localhost:8005", - "tester": "tester tests -s" + "tester": "tester ../tests -C -s" }, "minimum-stability": "stable", "config": { diff --git a/src/composer.lock b/src/composer.lock index f222f28..38115b3 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "098409b27b4e7e8f6c2b8d35935c5421", + "content-hash": "1d9cd435053b289e143b3aeb6f403fcf", "packages": [ { "name": "brick/math", @@ -1822,6 +1822,203 @@ ], "time": "2025-07-17T20:45:56+00:00" }, + { + "name": "fakerphp/faker", + "version": "v1.24.1", + "source": { + "type": "git", + "url": "https://github.com/FakerPHP/Faker.git", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0", + "psr/container": "^1.0 || ^2.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "conflict": { + "fzaninotto/faker": "*" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.4.1", + "doctrine/persistence": "^1.3 || ^2.0", + "ext-intl": "*", + "phpunit/phpunit": "^9.5.26", + "symfony/phpunit-bridge": "^5.4.16" + }, + "suggest": { + "doctrine/orm": "Required to use Faker\\ORM\\Doctrine", + "ext-curl": "Required by Faker\\Provider\\Image to download images.", + "ext-dom": "Required by Faker\\Provider\\HtmlLorem for generating random HTML.", + "ext-iconv": "Required by Faker\\Provider\\ru_RU\\Text::realText() for generating real Russian text.", + "ext-mbstring": "Required for multibyte Unicode string functionality." + }, + "type": "library", + "autoload": { + "psr-4": { + "Faker\\": "src/Faker/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "François Zaninotto" + } + ], + "description": "Faker is a PHP library that generates fake data for you.", + "keywords": [ + "data", + "faker", + "fixtures" + ], + "support": { + "issues": "https://github.com/FakerPHP/Faker/issues", + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" + }, + "time": "2024-11-21T13:46:39+00:00" + }, + { + "name": "hamcrest/hamcrest-php", + "version": "v2.1.1", + "source": { + "type": "git", + "url": "https://github.com/hamcrest/hamcrest-php.git", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/hamcrest/hamcrest-php/zipball/f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "reference": "f8b1c0173b22fa6ec77a81fe63e5b01eba7e6487", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0" + }, + "replace": { + "cordoval/hamcrest-php": "*", + "davedevelopment/hamcrest-php": "*", + "kodova/hamcrest-php": "*" + }, + "require-dev": { + "phpunit/php-file-iterator": "^1.4 || ^2.0 || ^3.0", + "phpunit/phpunit": "^4.8.36 || ^5.7 || ^6.5 || ^7.0 || ^8.0 || ^9.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.1-dev" + } + }, + "autoload": { + "classmap": [ + "hamcrest" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "description": "This is the PHP port of Hamcrest Matchers", + "keywords": [ + "test" + ], + "support": { + "issues": "https://github.com/hamcrest/hamcrest-php/issues", + "source": "https://github.com/hamcrest/hamcrest-php/tree/v2.1.1" + }, + "time": "2025-04-30T06:54:44+00:00" + }, + { + "name": "mockery/mockery", + "version": "1.6.12", + "source": { + "type": "git", + "url": "https://github.com/mockery/mockery.git", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mockery/mockery/zipball/1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "reference": "1f4efdd7d3beafe9807b08156dfcb176d18f1699", + "shasum": "" + }, + "require": { + "hamcrest/hamcrest-php": "^2.0.1", + "lib-pcre": ">=7.0", + "php": ">=7.3" + }, + "conflict": { + "phpunit/phpunit": "<8.0" + }, + "require-dev": { + "phpunit/phpunit": "^8.5 || ^9.6.17", + "symplify/easy-coding-standard": "^12.1.14" + }, + "type": "library", + "autoload": { + "files": [ + "library/helpers.php", + "library/Mockery.php" + ], + "psr-4": { + "Mockery\\": "library/Mockery" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Pádraic Brady", + "email": "padraic.brady@gmail.com", + "homepage": "https://github.com/padraic", + "role": "Author" + }, + { + "name": "Dave Marshall", + "email": "dave.marshall@atstsolutions.co.uk", + "homepage": "https://davedevelopment.co.uk", + "role": "Developer" + }, + { + "name": "Nathanael Esayeas", + "email": "nathanael.esayeas@protonmail.com", + "homepage": "https://github.com/ghostwriter", + "role": "Lead Developer" + } + ], + "description": "Mockery is a simple yet flexible PHP mock object framework", + "homepage": "https://github.com/mockery/mockery", + "keywords": [ + "BDD", + "TDD", + "library", + "mock", + "mock objects", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "docs": "https://docs.mockery.io/", + "issues": "https://github.com/mockery/mockery/issues", + "rss": "https://github.com/mockery/mockery/releases.atom", + "security": "https://github.com/mockery/mockery/security/advisories", + "source": "https://github.com/mockery/mockery" + }, + "time": "2024-05-16T03:13:13+00:00" + }, { "name": "nette/tester", "version": "v2.5.6", @@ -2214,6 +2411,59 @@ }, "time": "2025-07-21T12:19:29+00:00" }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" + }, + "time": "2021-11-05T16:47:00+00:00" + }, { "name": "slevomat/coding-standard", "version": "8.20.0", @@ -2431,6 +2681,73 @@ ], "time": "2025-06-17T22:17:01+00:00" }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, { "name": "symfony/thanks", "version": "v1.4.0", diff --git a/tester-bootstrap.php b/tester-bootstrap.php new file mode 100644 index 0000000..1374c0f --- /dev/null +++ b/tester-bootstrap.php @@ -0,0 +1,6 @@ + 'sqlite', + 'database' => ':memory:', +]); + +$database->query(' + CREATE TABLE `devices` ( + `uuid` TEXT NOT NULL, + `owner_uuid` TEXT NOT NULL, + `type` TEXT NOT NULL, + `operating_system` TEXT NOT NULL, + `hostname` TEXT NOT NULL, + PRIMARY KEY (`uuid`) + ) +'); + +$database->query(' + CREATE TABLE `owners` ( + `uuid` TEXT NOT NULL, + `firstname` TEXT NOT NULL, + `lastname` TEXT NOT NULL, + PRIMARY KEY (`uuid`) + ) +'); + +return $database; diff --git a/tests/app/Core/Owners/Creator.phpt b/tests/app/Core/Owners/Creator.phpt new file mode 100644 index 0000000..7544bda --- /dev/null +++ b/tests/app/Core/Owners/Creator.phpt @@ -0,0 +1,36 @@ +create( + $owner['firstname'], + $owner['lastname'], +); + + +$row = $database->query(' + SELECT `firstname`, `lastname` + FROM `owners` + WHERE `uuid` = %s', $uuid->toString(), ' +')->fetch(); + + +Tester\Assert::same( + $owner, + $row->toArray(), +); diff --git a/tests/app/Core/Owners/Repository.getOwner.phpt b/tests/app/Core/Owners/Repository.getOwner.phpt new file mode 100644 index 0000000..8c8eb1b --- /dev/null +++ b/tests/app/Core/Owners/Repository.getOwner.phpt @@ -0,0 +1,48 @@ +query('INSERT INTO `owners`', [ + 'uuid' => ($uuidOwner1 = Ramsey\Uuid\Uuid::uuid4())->toString(), + 'firstname' => 'Firstname 1', + 'lastname' => 'Lastname 1', +]); + +$database->query('INSERT INTO `owners`', [ + 'uuid' => ($uuidOwner2 = Ramsey\Uuid\Uuid::uuid4())->toString(), + 'firstname' => 'Firstname 2', + 'lastname' => 'Lastname 2', +]); + + +$repository = new WbAssignment\Core\Owners\Repository( + database: $database, +); + + +Tester\Assert::equal( + new WbAssignment\Core\Owners\Owner( + uuid: $uuidOwner1, + firstname: 'Firstname 1', + lastname: 'Lastname 1', + ), + $repository->getOwner($uuidOwner1), +); + +Tester\Assert::equal( + new WbAssignment\Core\Owners\Owner( + uuid: $uuidOwner2, + firstname: 'Firstname 2', + lastname: 'Lastname 2', + ), + $repository->getOwner($uuidOwner2), +); + +Tester\Assert::exception( + fn () => $repository->getOwner(Ramsey\Uuid\Uuid::uuid4()), + WbAssignment\Core\Owners\Exceptions\NotFoundException::class, +); diff --git a/tests/app/Core/Owners/Repository.listOwners.phpt b/tests/app/Core/Owners/Repository.listOwners.phpt new file mode 100644 index 0000000..c507436 --- /dev/null +++ b/tests/app/Core/Owners/Repository.listOwners.phpt @@ -0,0 +1,41 @@ +query('INSERT INTO `owners`', [ + 'uuid' => ($uuidOwner1 = Ramsey\Uuid\Uuid::uuid4())->toString(), + 'firstname' => 'Firstname 1', + 'lastname' => 'Lastname 1', +]); + +$database->query('INSERT INTO `owners`', [ + 'uuid' => ($uuidOwner2 = Ramsey\Uuid\Uuid::uuid4())->toString(), + 'firstname' => 'Firstname 2', + 'lastname' => 'Lastname 2', +]); + + +$repository = new WbAssignment\Core\Owners\Repository( + database: $database, +); + + +Tester\Assert::equal( + [ + new WbAssignment\Core\Owners\Owner( + uuid: $uuidOwner1, + firstname: 'Firstname 1', + lastname: 'Lastname 1', + ), + new WbAssignment\Core\Owners\Owner( + uuid: $uuidOwner2, + firstname: 'Firstname 2', + lastname: 'Lastname 2', + ), + ], + $repository->loadOwners(), +); diff --git a/tests/app/Core/Owners/dataProviders/Creator.php b/tests/app/Core/Owners/dataProviders/Creator.php new file mode 100644 index 0000000..01321fd --- /dev/null +++ b/tests/app/Core/Owners/dataProviders/Creator.php @@ -0,0 +1,16 @@ + 'John', + 'lastname' => 'Doe', + ], + ], + [ + [ + 'firstname' => 'Josef', + 'lastname' => 'Novák', + ], + ], +]; From 98196ebb8dc3b7d57db078f2eadf9b8db8eddae3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 15:45:00 +0200 Subject: [PATCH 4/6] Commands: manipulate with devices and owners via cli --- src/app/Core/Commands/AddDeviceCommand.php | 97 ++ src/app/Core/Commands/AddOwnerCommand.php | 69 + src/app/Core/Commands/ListDevicesCommand.php | 47 + src/app/Core/Commands/ListOwnersCommand.php | 47 + src/app/Core/SymfonyConsole/DI/Extension.php | 38 + src/bin/cliConsole.php | 12 + src/composer.json | 2 + src/composer.lock | 1544 +++++++++++++++--- src/config/common.neon | 1 + src/config/services.neon | 2 + 10 files changed, 1647 insertions(+), 212 deletions(-) create mode 100644 src/app/Core/Commands/AddDeviceCommand.php create mode 100644 src/app/Core/Commands/AddOwnerCommand.php create mode 100644 src/app/Core/Commands/ListDevicesCommand.php create mode 100644 src/app/Core/Commands/ListOwnersCommand.php create mode 100644 src/app/Core/SymfonyConsole/DI/Extension.php create mode 100644 src/bin/cliConsole.php diff --git a/src/app/Core/Commands/AddDeviceCommand.php b/src/app/Core/Commands/AddDeviceCommand.php new file mode 100644 index 0000000..a142b46 --- /dev/null +++ b/src/app/Core/Commands/AddDeviceCommand.php @@ -0,0 +1,97 @@ +setDescription('CLI command to add device'); + $this->setName('devices:add'); + } + + + + #[\Override] + protected function execute( + Symfony\Component\Console\Input\InputInterface $input, + Symfony\Component\Console\Output\OutputInterface $output, + ): int + { + $questionsHelper = new Symfony\Component\Console\Helper\QuestionHelper(); + + $hostname = $questionsHelper->ask( + $input, + $output, + new Symfony\Component\Console\Question\Question('Hostname: '), + ); + + $typeQuestion = new Symfony\Component\Console\Question\ChoiceQuestion( + 'Type:', + array_map( + static fn ($type) => $type->value, + WbAssignment\Core\Devices\Type::cases(), + ), + ); + $typeQuestion->setErrorMessage('Type is invalid.'); + $type = $questionsHelper->ask($input, $output, $typeQuestion); + + $operatingSystemQuestion = new Symfony\Component\Console\Question\ChoiceQuestion( + 'Operating System:', + array_map( + static fn ($type) => $type->value, + WbAssignment\Core\Devices\OperatingSystem::cases(), + ), + ); + $operatingSystemQuestion->setErrorMessage('Operating system is invalid.'); + $operatingSystem = $questionsHelper->ask($input, $output, $operatingSystemQuestion); + + $ownerQuestion = new Symfony\Component\Console\Question\ChoiceQuestion( + 'Owner:', + array_map( + static fn (WbAssignment\Core\Owners\Owner $owner) => $owner->uuid->toString(), + $this->repository->loadOwners(), + ), + ); + $ownerQuestion->setErrorMessage('Owner is invalid.'); + $ownerUuid = $questionsHelper->ask($input, $output, $ownerQuestion); + + $response = $this->guzzleClient->post('http://localhost/api/devices', [ + GuzzleHttp\RequestOptions::JSON => [ + 'hostname' => $hostname, + 'operating_system' => $operatingSystem, + 'owner_uuid' => $ownerUuid, + 'type' => $type, + ], + GuzzleHttp\RequestOptions::HEADERS => [ + 'Content-Type' => 'application/json', + ], + ]); + + if ($response->getStatusCode() !== 200) { + $output->writeln(sprintf('Invalid status code: %s', $response->getStatusCode())); + return Symfony\Component\Console\Command\Command::FAILURE; + } + + $output->writeln($response->getBody()->getContents()); + + return Symfony\Component\Console\Command\Command::SUCCESS; + } + +} diff --git a/src/app/Core/Commands/AddOwnerCommand.php b/src/app/Core/Commands/AddOwnerCommand.php new file mode 100644 index 0000000..e4c6927 --- /dev/null +++ b/src/app/Core/Commands/AddOwnerCommand.php @@ -0,0 +1,69 @@ +setDescription('CLI command to add owner'); + $this->setName('owners:add'); + } + + + + #[\Override] + protected function execute( + Symfony\Component\Console\Input\InputInterface $input, + Symfony\Component\Console\Output\OutputInterface $output, + ): int + { + $questionsHelper = new Symfony\Component\Console\Helper\QuestionHelper(); + + $firstname = $questionsHelper->ask( + $input, + $output, + new Symfony\Component\Console\Question\Question('Firstname: '), + ); + + $lastname = $questionsHelper->ask( + $input, + $output, + new Symfony\Component\Console\Question\Question('Lastname: '), + ); + + $response = $this->guzzleClient->post('http://localhost/api/owners', [ + GuzzleHttp\RequestOptions::JSON => [ + 'firstname' => $firstname, + 'lastname' => $lastname, + ], + GuzzleHttp\RequestOptions::HEADERS => [ + 'Content-Type' => 'application/json', + ], + ]); + + if ($response->getStatusCode() !== 200) { + $output->writeln(sprintf('Invalid status code: %s', $response->getStatusCode())); + return Symfony\Component\Console\Command\Command::FAILURE; + } + + $output->writeln($response->getBody()->getContents()); + + return Symfony\Component\Console\Command\Command::SUCCESS; + } + +} diff --git a/src/app/Core/Commands/ListDevicesCommand.php b/src/app/Core/Commands/ListDevicesCommand.php new file mode 100644 index 0000000..bd44119 --- /dev/null +++ b/src/app/Core/Commands/ListDevicesCommand.php @@ -0,0 +1,47 @@ +setDescription('CLI command to list devices'); + $this->setName('devices:list'); + } + + + + #[\Override] + protected function execute( + Symfony\Component\Console\Input\InputInterface $input, + Symfony\Component\Console\Output\OutputInterface $output, + ): int + { + $questionsHelper = $this->guzzleClient->get('http://localhost/api/devices'); + + if ($questionsHelper->getStatusCode() !== 200) { + $output->writeln(sprintf('Invalid status code: %s', $questionsHelper->getStatusCode())); + return Symfony\Component\Console\Command\Command::FAILURE; + } + + $output->writeln($questionsHelper->getBody()->getContents()); + + return Symfony\Component\Console\Command\Command::SUCCESS; + } + +} diff --git a/src/app/Core/Commands/ListOwnersCommand.php b/src/app/Core/Commands/ListOwnersCommand.php new file mode 100644 index 0000000..d07b27d --- /dev/null +++ b/src/app/Core/Commands/ListOwnersCommand.php @@ -0,0 +1,47 @@ +setDescription('CLI command to list owners'); + $this->setName('owners:list'); + } + + + + #[\Override] + protected function execute( + Symfony\Component\Console\Input\InputInterface $input, + Symfony\Component\Console\Output\OutputInterface $output, + ): int + { + $questionsHelper = $this->guzzleClient->get('http://localhost/api/owners'); + + if ($questionsHelper->getStatusCode() !== 200) { + $output->writeln(sprintf('Invalid status code: %s', $questionsHelper->getStatusCode())); + return Symfony\Component\Console\Command\Command::FAILURE; + } + + $output->writeln($questionsHelper->getBody()->getContents()); + + return Symfony\Component\Console\Command\Command::SUCCESS; + } + +} diff --git a/src/app/Core/SymfonyConsole/DI/Extension.php b/src/app/Core/SymfonyConsole/DI/Extension.php new file mode 100644 index 0000000..e06cd19 --- /dev/null +++ b/src/app/Core/SymfonyConsole/DI/Extension.php @@ -0,0 +1,38 @@ +getContainerBuilder(); + + $containerBuilder->addDefinition($this->prefix('symfonyApplication')) + ->setFactory(Symfony\Component\Console\Application::class); + + $this->compiler->addExportedType(Symfony\Component\Console\Application::class); + } + + + + #[\Override] + public function beforeCompile(): void + { + $containerBuilder = $this->getContainerBuilder(); + + /** @var Nette\DI\Definitions\ServiceDefinition $applicationDefinition */ + $applicationDefinition = $containerBuilder->getDefinition($this->prefix('symfonyApplication')); + + foreach ($containerBuilder->findByType(Symfony\Component\Console\Command\Command::class) as $service) { + $applicationDefinition->addSetup('add', [$service]); + } + } + +} diff --git a/src/bin/cliConsole.php b/src/bin/cliConsole.php new file mode 100644 index 0000000..8212916 --- /dev/null +++ b/src/bin/cliConsole.php @@ -0,0 +1,12 @@ +bootWebApplication() + ->getByType(Symfony\Component\Console\Application::class) + ->run(); + +exit($result); diff --git a/src/composer.json b/src/composer.json index 75e347f..b2c9bb1 100644 --- a/src/composer.json +++ b/src/composer.json @@ -6,6 +6,7 @@ "require": { "php": ">= 8.3", "dibi/dibi": "^5.1", + "guzzlehttp/guzzle": "^7.9", "latte/latte": "^3.0", "nette/application": "^3.2.3", "nette/assets": "^1.0.0", @@ -20,6 +21,7 @@ "nette/security": "^3.2", "nette/utils": "^4.0", "ramsey/uuid": "^4.9", + "symfony/console": "^7.3", "tracy/tracy": "^2.10" }, "require-dev": { diff --git a/src/composer.lock b/src/composer.lock index 38115b3..fd070ce 100644 --- a/src/composer.lock +++ b/src/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1d9cd435053b289e143b3aeb6f403fcf", + "content-hash": "979c71f8098b1fe86017e88151cadf18", "packages": [ { "name": "brick/math", @@ -140,6 +140,331 @@ }, "time": "2025-08-06T22:26:19+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.9.3", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "guzzle/client-integration-tests": "3.0.2", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:37:11+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.2.0", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2025-03-27T13:27:01+00:00" + }, + { + "name": "guzzlehttp/psr7", + "version": "2.7.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2025-03-27T12:30:47+00:00" + }, { "name": "latte/latte", "version": "v3.0.23", @@ -1496,53 +1821,31 @@ "time": "2025-08-06T21:43:34+00:00" }, { - "name": "ramsey/collection", - "version": "2.1.1", + "name": "psr/container", + "version": "2.0.2", "source": { "type": "git", - "url": "https://github.com/ramsey/collection.git", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", - "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", "shasum": "" }, "require": { - "php": "^8.1" - }, - "require-dev": { - "captainhook/plugin-composer": "^5.3", - "ergebnis/composer-normalize": "^2.45", - "fakerphp/faker": "^1.24", - "hamcrest/hamcrest-php": "^2.0", - "jangregor/phpstan-prophecy": "^2.1", - "mockery/mockery": "^1.6", - "php-parallel-lint/php-console-highlighter": "^1.0", - "php-parallel-lint/php-parallel-lint": "^1.4", - "phpspec/prophecy-phpunit": "^2.3", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^10.5", - "ramsey/coding-standard": "^2.3", - "ramsey/conventional-commits": "^1.6", - "roave/security-advisories": "dev-latest" + "php": ">=7.4.0" }, "type": "library", "extra": { - "captainhook": { - "force-install": true - }, - "ramsey/conventional-commits": { - "configFile": "conventional-commits.json" + "branch-alias": { + "dev-master": "2.0.x-dev" } }, "autoload": { "psr-4": { - "Ramsey\\Collection\\": "src/" + "Psr\\Container\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -1551,103 +1854,1040 @@ ], "authors": [ { - "name": "Ben Ramsey", - "email": "ben@benramsey.com", - "homepage": "https://benramsey.com" + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" } ], - "description": "A PHP library for representing and manipulating collections.", + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", "keywords": [ - "array", - "collection", - "hash", - "map", - "queue", - "set" + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" ], "support": { - "issues": "https://github.com/ramsey/collection/issues", - "source": "https://github.com/ramsey/collection/tree/2.1.1" + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/2.0.2" }, - "time": "2025-03-22T05:38:12+00:00" + "time": "2021-11-05T16:47:00+00:00" }, { - "name": "ramsey/uuid", - "version": "4.9.0", + "name": "psr/http-client", + "version": "1.0.3", "source": { "type": "git", - "url": "https://github.com/ramsey/uuid.git", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + "url": "https://github.com/php-fig/http-client.git", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", - "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", - "php": "^8.0", - "ramsey/collection": "^1.2 || ^2.0" + "php": "^7.0 || ^8.0", + "psr/http-message": "^1.0 || ^2.0" }, - "replace": { + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Client\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP clients", + "homepage": "https://github.com/php-fig/http-client", + "keywords": [ + "http", + "http-client", + "psr", + "psr-18" + ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, + "time": "2023-09-23T14:17:50+00:00" + }, + { + "name": "psr/http-factory", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "shasum": "" + }, + "require": { + "php": ">=7.1", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, + { + "name": "ramsey/collection", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/ramsey/collection.git", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/collection/zipball/344572933ad0181accbf4ba763e85a0306a8c5e2", + "reference": "344572933ad0181accbf4ba763e85a0306a8c5e2", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "captainhook/plugin-composer": "^5.3", + "ergebnis/composer-normalize": "^2.45", + "fakerphp/faker": "^1.24", + "hamcrest/hamcrest-php": "^2.0", + "jangregor/phpstan-prophecy": "^2.1", + "mockery/mockery": "^1.6", + "php-parallel-lint/php-console-highlighter": "^1.0", + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpspec/prophecy-phpunit": "^2.3", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5", + "ramsey/coding-standard": "^2.3", + "ramsey/conventional-commits": "^1.6", + "roave/security-advisories": "dev-latest" + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + }, + "ramsey/conventional-commits": { + "configFile": "conventional-commits.json" + } + }, + "autoload": { + "psr-4": { + "Ramsey\\Collection\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Ramsey", + "email": "ben@benramsey.com", + "homepage": "https://benramsey.com" + } + ], + "description": "A PHP library for representing and manipulating collections.", + "keywords": [ + "array", + "collection", + "hash", + "map", + "queue", + "set" + ], + "support": { + "issues": "https://github.com/ramsey/collection/issues", + "source": "https://github.com/ramsey/collection/tree/2.1.1" + }, + "time": "2025-03-22T05:38:12+00:00" + }, + { + "name": "ramsey/uuid", + "version": "4.9.0", + "source": { + "type": "git", + "url": "https://github.com/ramsey/uuid.git", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "reference": "4e0e23cc785f0724a0e838279a9eb03f28b092a0", + "shasum": "" + }, + "require": { + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12 || ^0.13", + "php": "^8.0", + "ramsey/collection": "^1.2 || ^2.0" + }, + "replace": { "rhumsaa/uuid": "self.version" }, - "require-dev": { - "captainhook/captainhook": "^5.25", - "captainhook/plugin-composer": "^5.3", - "dealerdirect/phpcodesniffer-composer-installer": "^1.0", - "ergebnis/composer-normalize": "^2.47", - "mockery/mockery": "^1.6", - "paragonie/random-lib": "^2", - "php-mock/php-mock": "^2.6", - "php-mock/php-mock-mockery": "^1.5", - "php-parallel-lint/php-parallel-lint": "^1.4.0", - "phpbench/phpbench": "^1.2.14", - "phpstan/extension-installer": "^1.4", - "phpstan/phpstan": "^2.1", - "phpstan/phpstan-mockery": "^2.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpunit/phpunit": "^9.6", - "slevomat/coding-standard": "^8.18", - "squizlabs/php_codesniffer": "^3.13" + "require-dev": { + "captainhook/captainhook": "^5.25", + "captainhook/plugin-composer": "^5.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "ergebnis/composer-normalize": "^2.47", + "mockery/mockery": "^1.6", + "paragonie/random-lib": "^2", + "php-mock/php-mock": "^2.6", + "php-mock/php-mock-mockery": "^1.5", + "php-parallel-lint/php-parallel-lint": "^1.4.0", + "phpbench/phpbench": "^1.2.14", + "phpstan/extension-installer": "^1.4", + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-mockery": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^9.6", + "slevomat/coding-standard": "^8.18", + "squizlabs/php_codesniffer": "^3.13" + }, + "suggest": { + "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", + "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", + "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", + "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", + "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + }, + "type": "library", + "extra": { + "captainhook": { + "force-install": true + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Ramsey\\Uuid\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "keywords": [ + "guid", + "identifier", + "uuid" + ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "source": "https://github.com/ramsey/uuid/tree/4.9.0" + }, + "time": "2025-06-25T14:20:11+00:00" + }, + { + "name": "symfony/console", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/5f360ebc65c55265a74d23d7fe27f957870158a1", + "reference": "5f360ebc65c55265a74d23d7fe27f957870158a1", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^7.2" + }, + "conflict": { + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "support": { + "source": "https://github.com/symfony/console/tree/v7.3.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-30T17:13:41+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", + "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-25T14:21:43+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638", + "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "reference": "b9123926e3b7bc2f98c02ad54f6a4b02b91a8abe", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "3833d7255cc303546435cb650316bff708a1c75c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", + "reference": "3833d7255cc303546435cb650316bff708a1c75c", + "shasum": "" + }, + "require": { + "php": ">=7.2" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-09-09T11:45:10+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.32.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6d857f4d76bd4b343eac26d6b539585d2bc56493", + "reference": "6d857f4d76bd4b343eac26d6b539585d2bc56493", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": ">=7.2" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.32.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-12-23T08:48:59+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.6.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4", + "shasum": "" }, - "suggest": { - "ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.", - "ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.", - "ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.", - "paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter", - "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" }, "type": "library", "extra": { - "captainhook": { - "force-install": true + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, + "branch-alias": { + "dev-main": "3.6-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v3.6.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" } + ], + "time": "2025-04-25T09:37:31+00:00" + }, + { + "name": "symfony/string", + "version": "v7.3.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/42f505aff654e62ac7ac2ce21033818297ca89ca", + "reference": "42f505aff654e62ac7ac2ce21033818297ca89ca", + "shasum": "" + }, + "require": { + "php": ">=8.2", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/emoji": "^7.1", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.4|^7.0" + }, + "type": "library", "autoload": { "files": [ - "src/functions.php" + "Resources/functions.php" ], "psr-4": { - "Ramsey\\Uuid\\": "src/" - } + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "A PHP library for generating and working with universally unique identifiers (UUIDs).", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", "keywords": [ - "guid", - "identifier", - "uuid" + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" ], "support": { - "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.9.0" + "source": "https://github.com/symfony/string/tree/v7.3.2" }, - "time": "2025-06-25T14:20:11+00:00" + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2025-07-10T08:47:49+00:00" }, { "name": "tracy/tracy", @@ -2411,59 +3651,6 @@ }, "time": "2025-07-21T12:19:29+00:00" }, - { - "name": "psr/container", - "version": "2.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-fig/container.git", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", - "shasum": "" - }, - "require": { - "php": ">=7.4.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Container\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common Container Interface (PHP FIG PSR-11)", - "homepage": "https://github.com/php-fig/container", - "keywords": [ - "PSR-11", - "container", - "container-interface", - "container-interop", - "psr" - ], - "support": { - "issues": "https://github.com/php-fig/container/issues", - "source": "https://github.com/php-fig/container/tree/2.0.2" - }, - "time": "2021-11-05T16:47:00+00:00" - }, { "name": "slevomat/coding-standard", "version": "8.20.0", @@ -2681,73 +3868,6 @@ ], "time": "2025-06-17T22:17:01+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "v3.6.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", - "shasum": "" - }, - "require": { - "php": ">=8.1" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/contracts", - "name": "symfony/contracts" - }, - "branch-alias": { - "dev-main": "3.6-dev" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:21:43+00:00" - }, { "name": "symfony/thanks", "version": "v1.4.0", diff --git a/src/config/common.neon b/src/config/common.neon index 0695518..1738128 100644 --- a/src/config/common.neon +++ b/src/config/common.neon @@ -28,3 +28,4 @@ dibi: %dibi% extensions: dibi: Dibi\Bridges\Nette\DibiExtension22 + symfony_console: WbAssignemnt\Core\SymfonyConsole\DI\Extension diff --git a/src/config/services.neon b/src/config/services.neon index b04bdf7..2a511ab 100644 --- a/src/config/services.neon +++ b/src/config/services.neon @@ -1,10 +1,12 @@ services: - WbAssignment\Core\RouterFactory::createRouter + - GuzzleHttp\Client search: - in: %appDir% classes: + - *Command - *Creator - *Facade - *Factory From ac7abe9d86dba275386ffbd49fc59ad2b2c60134 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 15:52:00 +0200 Subject: [PATCH 5/6] Docker: initiate database upon docker-composer up --- docker-compose.yml | 12 ++++++++++-- schema/{mydb.sql => 01_mydb.sql} | 0 src/app/Bootstrap.php | 4 ++++ 3 files changed, 14 insertions(+), 2 deletions(-) rename schema/{mydb.sql => 01_mydb.sql} (100%) diff --git a/docker-compose.yml b/docker-compose.yml index 1bb3d81..a9570cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,12 +3,13 @@ services: build: dockerfile: ./docker/DockerfilePhp ports: - - 8090:80 + - "8090:80" volumes: - ./docker/apache.conf:/etc/apache2/sites-available/000-default.conf - ./src:/var/www/html depends_on: - - database + database: + condition: service_healthy database: image: mariadb:10.4 @@ -20,3 +21,10 @@ services: MYSQL_PASSWORD: mypass ports: - "3306:3306" + volumes: + - ./schema/:/docker-entrypoint-initdb.d:ro + healthcheck: + test: [ "CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-pRootpass" ] + interval: 5s + timeout: 3s + retries: 20 diff --git a/schema/mydb.sql b/schema/01_mydb.sql similarity index 100% rename from schema/mydb.sql rename to schema/01_mydb.sql diff --git a/src/app/Bootstrap.php b/src/app/Bootstrap.php index f6db9ec..c676c3f 100644 --- a/src/app/Bootstrap.php +++ b/src/app/Bootstrap.php @@ -34,6 +34,10 @@ public function bootWebApplication(): Nette\DI\Container public function initializeEnvironment(): void { + if (file_exists($this->rootDir . '/log') === FALSE) { + mkdir($this->rootDir . '/log'); + } + //$this->configurator->setDebugMode('secret@23.75.345.200'); // enable for your remote IP $this->configurator->enableTracy($this->rootDir . '/log'); From 1fe385a46c87c911d030c26e450683b9751293af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20B=C5=99e=C4=8Dka?= Date: Sun, 10 Aug 2025 16:02:00 +0200 Subject: [PATCH 6/6] Readme file --- Makefile | 4 ++ readme.md | 58 +++++++++++++++++++++++++ src/app/Presentation/Home/default.latte | 34 +-------------- 3 files changed, 64 insertions(+), 32 deletions(-) create mode 100644 readme.md diff --git a/Makefile b/Makefile index 988f966..ba9f2f5 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ codesniffer: codesnifferFix: cd ./src && composer codesnifferFix +.PHONE composerInstall: +composerInstall: + cd ./src && composer install + .PHONE composerValidate: composerValidate: cd ./src && composer validate --strict diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..bee326c --- /dev/null +++ b/readme.md @@ -0,0 +1,58 @@ +## WB assignment + +### How to setup app + +``` +make composerInstall + +make dockerComposeUp +``` + + +### Using API service + +There are 2 ways how to use this API service. + +#### REST API + +- `curl` requests to the: + - `GET http://localhost:8090/api/devices`: list devices (output JSON formatted) + - `POST http://localhost:8090/api/devices`: create device. Request example: + ```json + { + "hostname": "", + "operating_system": "", + "owner_uuid": "", + "type": "" + } + ``` + - GET `http://localhost:8090/api/owners`: list devices (output JSON formatted) + - POST `http://localhost:8090/api/owners`: create owner. Request example: + ```json + { + "firstname": "", + "lastname": "" + } + ``` + +#### Console Commands + +```sh +cd /var/www/html/bin + +php cliConsole.php owners:add + +php cliConsole.php owners:list + +php cliConsole.php devices:add + +php cliConsole.php devices:list +``` + + +### Options to improve + +- Extract hardcoded DB credentials from code +- Properly use MySQL database even for unit tests instead or using in-memory sqlite +- Audit logging when changing data +- Authorization and authentication of users diff --git a/src/app/Presentation/Home/default.latte b/src/app/Presentation/Home/default.latte index f5c31ba..8ea7ba3 100644 --- a/src/app/Presentation/Home/default.latte +++ b/src/app/Presentation/Home/default.latte @@ -1,38 +1,8 @@ -{* This is the welcome page, you can delete it *} - {block content}
-

You have successfully created your Nette Web project.

- -

- If you are exploring Nette for the first time, you should read the - Quick Start, documentation, - blog and forum.

- -

We hope you enjoy Nette!

+

Not much to see here bro..... Either use CLI commands or fire Postman commands to enpoints "/api/devices" and "/api/owners".

- -