Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ jobs:

- name: Run phpstan
run: make phpstan

- name: Run tester
run: make tester
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -17,3 +21,7 @@ dockerComposeUp:
.PHONE phpstan:
phpstan:
cd ./src && composer phpstan

.PHONE tester:
tester:
cd ./src && composer tester
12 changes: 10 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
4 changes: 4 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
58 changes: 58 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions schema/01_mydb.sql
Original file line number Diff line number Diff line change
@@ -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;
4 changes: 4 additions & 0 deletions src/app/Bootstrap.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');

Expand Down
97 changes: 97 additions & 0 deletions src/app/Core/Commands/AddDeviceCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php declare(strict_types=1);

namespace WbAssignment\Core\Commands;

use GuzzleHttp;
use Symfony;
use WbAssignment;


class AddDeviceCommand extends Symfony\Component\Console\Command\Command
{

public function __construct(
private readonly GuzzleHttp\Client $guzzleClient,
private readonly WbAssignment\Core\Owners\Repository $repository,
) {
parent::__construct();
}



#[\Override]
protected function configure(): void
{
$this->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;
}

}
69 changes: 69 additions & 0 deletions src/app/Core/Commands/AddOwnerCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php declare(strict_types=1);

namespace WbAssignment\Core\Commands;

use GuzzleHttp;
use Symfony;


class AddOwnerCommand extends Symfony\Component\Console\Command\Command
{

public function __construct(
private readonly GuzzleHttp\Client $guzzleClient,
) {
parent::__construct();
}



#[\Override]
protected function configure(): void
{
$this->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;
}

}
47 changes: 47 additions & 0 deletions src/app/Core/Commands/ListDevicesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types=1);

namespace WbAssignment\Core\Commands;

use GuzzleHttp;
use Symfony;


class ListDevicesCommand extends Symfony\Component\Console\Command\Command
{

public function __construct(
private readonly GuzzleHttp\Client $guzzleClient,
) {
parent::__construct();
}



#[\Override]
protected function configure(): void
{
$this->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;
}

}
Loading