Compare commits
No commits in common. "master" and "feature_usetreno_api" have entirely different histories.
master
...
feature_us
30
README.md
30
README.md
@ -1,34 +1,6 @@
|
|||||||
symfony_example_app
|
|
||||||
===================
|
|
||||||
|
|
||||||
Generátor QR kódů pro platby z https://topapi.top-test.cz
|
|
||||||
|
|
||||||
Quick setup
|
|
||||||
-----------
|
|
||||||
```bash
|
|
||||||
# git clone https://git.nanobyte.cz/nanobyte-public/symfony_example_app.git
|
|
||||||
# cd symfony_example_app
|
|
||||||
# docker compose run --build php-fpm composer install
|
|
||||||
# docker compose up --build
|
|
||||||
```
|
|
||||||
|
|
||||||
- aplikace je dostupná na http://localhost:8000/
|
|
||||||
- na adrese http://localhost:3000/explore je běžící grafana (s loki a tempo)
|
|
||||||
- aplikace je nastavená (i na lokálu, běžně bych to nastavil až na devech/stage/PROD) aby posílala logy do lokiho a tracing do tempa pomocí otel protokolu
|
|
||||||
|
|
||||||
TODO:
|
TODO:
|
||||||
-----
|
-----
|
||||||
- [ ] Chybí speciální slovník nebo vypnutí slovníku pro testy
|
- [ ] Chybí speciální slovník nebo vypnutí slovníku pro testy
|
||||||
- [ ] V reálný aplikaci bych použil Mockery, nicméně tady mě to přijde zbytečný
|
- [ ] V reálný aplikaci bych použil Mockery, nicméně tady mě to přijde zbytečný
|
||||||
- [ ] Nastavení cache ideálně v memcached/redis etc.
|
- [ ] Nastavení cache ideálně v memcached/redis etc.
|
||||||
- [ ] Vyhezkat OTEL logs, OTEL tracing
|
- [ ] Vylepsit OTEL logs
|
||||||
- [ ] CI pipelines
|
|
||||||
- [ ] k8s deployment
|
|
||||||
- [ ] prometheus country na počet requestů/api (počet 200OK/500ERR)
|
|
||||||
- [ ] Je dost na zvážení zda nezobrazit výsledek remote validace (response_create_400.json) a nenechat uživatele špatné hodnoty opravit. Ovšem znamená to že lokální validátory jsou špatně, chybu by bylo vhodné zalogovat do Sentry (do logu etc.) a opravit ji...
|
|
||||||
- [ ] Na produkci bych statický content rozhodně netlačil přes app container ale přes static nginx container (asset-map:compile -> copy do nginx static containeru)
|
|
||||||
- [ ] xdebug v dockeru
|
|
||||||
|
|
||||||
Poznámky
|
|
||||||
--------
|
|
||||||
- Nejsem kodér (a javascript developer), nevypadá to nijak extra ;-)
|
|
||||||
|
@ -31,7 +31,7 @@ services:
|
|||||||
arguments:
|
arguments:
|
||||||
$available_currencies: '%app.currencies%'
|
$available_currencies: '%app.currencies%'
|
||||||
|
|
||||||
App\Service\StubQRCodeProvider:
|
App\Service\StubQRCodeGenerator:
|
||||||
arguments:
|
arguments:
|
||||||
$imagePath: '%kernel.project_dir%/assets/images/wip.png'
|
$imagePath: '%kernel.project_dir%/assets/images/wip.png'
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ services:
|
|||||||
$retryCount: 2
|
$retryCount: 2
|
||||||
$retryWaitSeconds: 0.5
|
$retryWaitSeconds: 0.5
|
||||||
|
|
||||||
App\Service\CachedQRCodeProvider:
|
App\Service\CachedQRCodeGenerator:
|
||||||
arguments:
|
arguments:
|
||||||
$innerGenerator: '@App\Service\Remote\UsetrenoQRCodeProvider'
|
$innerGenerator: '@App\Service\Remote\UsetrenoQRCodeProvider'
|
||||||
$cacheDuration: 'PT60S'
|
$cacheDuration: 'PT60S'
|
||||||
@ -55,7 +55,7 @@ services:
|
|||||||
|
|
||||||
App\Service\CurrencyListerInterface: '@App\Service\StaticCurrencyLister'
|
App\Service\CurrencyListerInterface: '@App\Service\StaticCurrencyLister'
|
||||||
App\Service\QRCodeQROptionsProviderInterface: '@App\Service\QRCodeQROptionsDefaultProvider'
|
App\Service\QRCodeQROptionsProviderInterface: '@App\Service\QRCodeQROptionsDefaultProvider'
|
||||||
App\Service\QRCodeProviderInterface: '@App\Service\CachedQRCodeProvider'
|
App\Service\QRCodeGeneratorInterface: '@App\Service\CachedQRCodeGenerator'
|
||||||
|
|
||||||
# add more service definitions when explicit configuration is needed
|
# add more service definitions when explicit configuration is needed
|
||||||
# please note that last definitions always *replace* previous ones
|
# please note that last definitions always *replace* previous ones
|
||||||
|
@ -5,7 +5,7 @@ namespace App\Controller;
|
|||||||
use App\Entity\Input\QRCode\QRCode;
|
use App\Entity\Input\QRCode\QRCode;
|
||||||
use App\Form\Type\QRCodeType;
|
use App\Form\Type\QRCodeType;
|
||||||
use App\Service\DTO\QRCodeEntityConverter;
|
use App\Service\DTO\QRCodeEntityConverter;
|
||||||
use App\Service\QRCodeProviderInterface;
|
use App\Service\QRCodeGeneratorInterface;
|
||||||
use App\Service\QRCodeQROptionsProviderInterface;
|
use App\Service\QRCodeQROptionsProviderInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
@ -16,8 +16,8 @@ class IndexController extends AbstractController {
|
|||||||
public function __construct(private readonly QRCodeQROptionsProviderInterface $qrCodeQROptionsFactory, private readonly QRCodeEntityConverter $codeEntityConverter) {}
|
public function __construct(private readonly QRCodeQROptionsProviderInterface $qrCodeQROptionsFactory, private readonly QRCodeEntityConverter $codeEntityConverter) {}
|
||||||
#[Route('/', name: 'homepage')]
|
#[Route('/', name: 'homepage')]
|
||||||
public function indexAction(
|
public function indexAction(
|
||||||
Request $request,
|
Request $request,
|
||||||
QRCodeProviderInterface $qrCodeGenerator,
|
QRCodeGeneratorInterface $qrCodeGenerator,
|
||||||
): Response
|
): Response
|
||||||
{
|
{
|
||||||
$qrCodeImage = null;
|
$qrCodeImage = null;
|
||||||
|
@ -23,7 +23,7 @@ class QRCodeMoneyType extends AbstractType
|
|||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
$builder
|
$builder
|
||||||
->add('amount', NumberType::class, ['invalid_message' => 'messages.not_valid_amount'])
|
->add('amount', NumberType::class)
|
||||||
->add('currency', ChoiceType::class, [
|
->add('currency', ChoiceType::class, [
|
||||||
'choices' => array_combine(
|
'choices' => array_combine(
|
||||||
iterator_to_array($this->currencyLister->getCurrencies()),
|
iterator_to_array($this->currencyLister->getCurrencies()),
|
||||||
|
@ -5,7 +5,7 @@ namespace App\Service;
|
|||||||
|
|
||||||
use App\Entity\DTO\QRCode\QRCode;
|
use App\Entity\DTO\QRCode\QRCode;
|
||||||
|
|
||||||
interface CacheableQRCodeProviderInterface extends QRCodeProviderInterface
|
interface CacheableQRCodeGeneratorInterface extends QRCodeGeneratorInterface
|
||||||
{
|
{
|
||||||
// We can't calculate cache key directly from QRCode
|
// We can't calculate cache key directly from QRCode
|
||||||
public function getCacheKey(QRCode $entity): string;
|
public function getCacheKey(QRCode $entity): string;
|
@ -10,14 +10,14 @@ use Symfony\Contracts\Cache\CacheInterface;
|
|||||||
use Symfony\Contracts\Cache\ItemInterface;
|
use Symfony\Contracts\Cache\ItemInterface;
|
||||||
|
|
||||||
|
|
||||||
final readonly class CachedQRCodeProvider implements QRCodeProviderInterface
|
final readonly class CachedQRCodeGenerator implements QRCodeGeneratorInterface
|
||||||
{
|
{
|
||||||
private \DateInterval $cacheDuration;
|
private \DateInterval $cacheDuration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function __construct(private CacheableQRCodeProviderInterface $innerGenerator, private CacheInterface $cache, private LoggerInterface $logger, string $cacheDuration)
|
public function __construct(private CacheableQRCodeGeneratorInterface $innerGenerator, private CacheInterface $cache, private LoggerInterface $logger, string $cacheDuration)
|
||||||
{
|
{
|
||||||
$this->cacheDuration = new \DateInterval($cacheDuration);
|
$this->cacheDuration = new \DateInterval($cacheDuration);
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ use App\Entity\DTO\QRCode\QRCode;
|
|||||||
use App\Entity\DTO\QRCode\QRCodeMoney;
|
use App\Entity\DTO\QRCode\QRCodeMoney;
|
||||||
use App\Entity\DTO\QRCode\QRCodePaymentIdentification;
|
use App\Entity\DTO\QRCode\QRCodePaymentIdentification;
|
||||||
use App\Entity\DTO\QRCode\QRCodeQROptions;
|
use App\Entity\DTO\QRCode\QRCodeQROptions;
|
||||||
use Nubium\Exception\ThisShouldNeverHappenException;
|
|
||||||
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
use Symfony\Component\Validator\Validator\ValidatorInterface;
|
||||||
|
|
||||||
final readonly class QRCodeEntityConverter
|
final readonly class QRCodeEntityConverter
|
||||||
@ -21,11 +20,11 @@ final readonly class QRCodeEntityConverter
|
|||||||
}
|
}
|
||||||
|
|
||||||
return new QRCode(
|
return new QRCode(
|
||||||
$code->getIban() ?? throw new ThisShouldNeverHappenException("iban not set"),
|
$code->getIban() ?? throw new \InvalidArgumentException("iban not set"),
|
||||||
\DateTimeImmutable::createFromMutable($code->getDueDate() ?? throw new ThisShouldNeverHappenException("due date not set")),
|
\DateTimeImmutable::createFromMutable($code->getDueDate() ?? throw new \InvalidArgumentException("due date not set")),
|
||||||
$code->getMessage() ?? throw new ThisShouldNeverHappenException("message not set"),
|
$code->getMessage() ?? throw new \InvalidArgumentException("message not set"),
|
||||||
$this->convertMoney($code->getMoney() ?? throw new ThisShouldNeverHappenException("money not set")),
|
$this->convertMoney($code->getMoney() ?? throw new \InvalidArgumentException("money not set")),
|
||||||
$this->convertCodeOptions($code->getCodeQROptions() ?? throw new ThisShouldNeverHappenException("codeQROptions not set")),
|
$this->convertCodeOptions($code->getCodeQROptions() ?? throw new \InvalidArgumentException("codeQROptions not set")),
|
||||||
$this->convertIdentification($code->getPaymentIdentification())
|
$this->convertIdentification($code->getPaymentIdentification())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -33,8 +32,8 @@ final readonly class QRCodeEntityConverter
|
|||||||
protected function convertMoney(\App\Entity\Input\QRCode\QRCodeMoney $money): QRCodeMoney
|
protected function convertMoney(\App\Entity\Input\QRCode\QRCodeMoney $money): QRCodeMoney
|
||||||
{
|
{
|
||||||
return new QRCodeMoney(
|
return new QRCodeMoney(
|
||||||
$money->getAmount() ?? throw new ThisShouldNeverHappenException("amount not set"),
|
$money->getAmount() ?? throw new \InvalidArgumentException("amount not set"),
|
||||||
$money->getCurrency() ?? throw new ThisShouldNeverHappenException("currency not set")
|
$money->getCurrency() ?? throw new \InvalidArgumentException("currency not set")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ namespace App\Service;
|
|||||||
use App\Entity\DTO\QRCode\QRCode;
|
use App\Entity\DTO\QRCode\QRCode;
|
||||||
use App\Service\Exception\QRCodeGeneratorException;
|
use App\Service\Exception\QRCodeGeneratorException;
|
||||||
|
|
||||||
interface QRCodeProviderInterface
|
interface QRCodeGeneratorInterface
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Generates QR code from entity
|
* Generates QR code from entity
|
@ -4,7 +4,7 @@ declare(strict_types=1);
|
|||||||
namespace App\Service\Remote;
|
namespace App\Service\Remote;
|
||||||
|
|
||||||
use App\Entity\DTO\QRCode\QRCode;
|
use App\Entity\DTO\QRCode\QRCode;
|
||||||
use App\Service\CacheableQRCodeProviderInterface;
|
use App\Service\CacheableQRCodeGeneratorInterface;
|
||||||
use App\Service\Remote\Edge\QRCodeEntityConverter;
|
use App\Service\Remote\Edge\QRCodeEntityConverter;
|
||||||
use App\Service\Remote\Exception\UsetrenoQRCodeException;
|
use App\Service\Remote\Exception\UsetrenoQRCodeException;
|
||||||
use App\Service\Remote\Exception\UsetrenoQRCodeRemoteServerErrorException;
|
use App\Service\Remote\Exception\UsetrenoQRCodeRemoteServerErrorException;
|
||||||
@ -12,7 +12,7 @@ use Nubium\Exception\ThisShouldNeverHappenException;
|
|||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
use Symfony\Component\HttpClient\Exception\TransportException;
|
use Symfony\Component\HttpClient\Exception\TransportException;
|
||||||
|
|
||||||
class UsetrenoQRCodeProvider implements CacheableQRCodeProviderInterface
|
class UsetrenoQRCodeProvider implements CacheableQRCodeGeneratorInterface
|
||||||
{
|
{
|
||||||
use RetryingFailClientTrait;
|
use RetryingFailClientTrait;
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ namespace App\Service;
|
|||||||
|
|
||||||
use App\Entity\DTO\QRCode\QRCode;
|
use App\Entity\DTO\QRCode\QRCode;
|
||||||
|
|
||||||
readonly final class StubQRCodeProvider implements QRCodeProviderInterface
|
readonly final class StubQRCodeGenerator implements QRCodeGeneratorInterface
|
||||||
{
|
{
|
||||||
public function __construct(private string $imagePath) {}
|
public function __construct(private string $imagePath) {}
|
||||||
|
|
@ -1,12 +0,0 @@
|
|||||||
{% extends 'base.html.twig' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<p>
|
|
||||||
<div class="container">
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<h1>{{ "page not found" | trans }}</h1>
|
|
||||||
<a href="{{ path('homepage') }}">{{ "return to the homepage" | trans }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
@ -1,13 +0,0 @@
|
|||||||
{% extends 'base.html.twig' %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<p>
|
|
||||||
<div class="container">
|
|
||||||
<div style="text-align: center;">
|
|
||||||
<h1>{{ "internal server error" | trans }}</h1>
|
|
||||||
{{ "our developers are doing their best to fix it. please try again later." | trans }}
|
|
||||||
<a href="{{ path('homepage') }}">{{ "return to the homepage" | trans }}</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
{% endblock %}
|
|
@ -3,7 +3,7 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Tests\Controller;
|
namespace App\Tests\Controller;
|
||||||
|
|
||||||
use App\Service\QRCodeProviderInterface;
|
use App\Service\QRCodeGeneratorInterface;
|
||||||
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
use Symfony\Bundle\FrameworkBundle\KernelBrowser;
|
||||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ class IndexControllerTest extends WebTestCase
|
|||||||
|
|
||||||
private function generateClientForSubmitTest(): KernelBrowser
|
private function generateClientForSubmitTest(): KernelBrowser
|
||||||
{
|
{
|
||||||
$mock = $this->createMock(QRCodeProviderInterface::class);
|
$mock = $this->createMock(QRCodeGeneratorInterface::class);
|
||||||
$mock->expects($this->any())
|
$mock->expects($this->any())
|
||||||
->method('generateQRCodeFromEntity')
|
->method('generateQRCodeFromEntity')
|
||||||
->will($this->returnValue('foo'));
|
->will($this->returnValue('foo'));
|
||||||
@ -85,7 +85,7 @@ class IndexControllerTest extends WebTestCase
|
|||||||
$client = static::createClient();
|
$client = static::createClient();
|
||||||
$client->disableReboot();
|
$client->disableReboot();
|
||||||
|
|
||||||
self::getContainer()->set('App\Service\QRCodeProviderInterface', $mock);
|
self::getContainer()->set('App\Service\QRCodeGeneratorInterface', $mock);
|
||||||
|
|
||||||
return $client;
|
return $client;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,3 @@ Generate qr code: "Vygenerovat QR kód"
|
|||||||
Iban: "IBAN"
|
Iban: "IBAN"
|
||||||
QR code generator: "Generátor bankovních QR kódů"
|
QR code generator: "Generátor bankovních QR kódů"
|
||||||
You can generate another QR code here: "Další QR kód si můžete vygenerovat zde"
|
You can generate another QR code here: "Další QR kód si můžete vygenerovat zde"
|
||||||
page not found: 'Stránka nenalezena'
|
|
||||||
return to the homepage: 'Zpět na homepage'
|
|
||||||
"our developers are doing their best to fix it. please try again later.": "Naši programátoři dělají maximum aby to opravili. Zkuste to prosím později."
|
|
||||||
internal server error: 'Něco se pokazilo :-('
|
|
||||||
|
Loading…
Reference in New Issue
Block a user