mirror of
https://gitlab.com/Chill-Projet/chill-bundles.git
synced 2025-06-07 18:44:08 +00:00
First step to async generation [WIP]
This commit is contained in:
parent
732b7dc8f7
commit
057c34610d
@ -5,6 +5,10 @@ framework:
|
||||
|
||||
# Uncomment this (and the failed transport below) to send failed messages to this transport for later handling.
|
||||
failure_transport: failed
|
||||
buses:
|
||||
messenger.bus.default:
|
||||
middleware:
|
||||
- 'Chill\MainBundle\Messenger\Middleware\AuthenticationMiddleware'
|
||||
|
||||
transports:
|
||||
# those transports are added by chill-bundles recipes
|
||||
@ -19,7 +23,9 @@ framework:
|
||||
async: ~
|
||||
auto_setup: true
|
||||
|
||||
priority: '%env(MESSENGER_TRANSPORT_DSN)%/priority'
|
||||
priority:
|
||||
dsn: '%env(MESSENGER_TRANSPORT_DSN)%/priority'
|
||||
|
||||
# end of transports added by chill-bundles recipes
|
||||
# https://symfony.com/doc/current/messenger.html#transport-configuration
|
||||
failed: 'doctrine://default?queue_name=failed'
|
||||
@ -61,6 +67,7 @@ framework:
|
||||
'Chill\MainBundle\Workflow\Messenger\PostSignatureStateChangeMessage': priority
|
||||
'Chill\MainBundle\Workflow\Messenger\PostPublicViewMessage': async
|
||||
'Chill\MainBundle\Service\Workflow\CancelStaleWorkflowMessage': async
|
||||
'Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage': priority
|
||||
# end of routes added by chill-bundles recipes
|
||||
# Route your messages to the transports
|
||||
# 'App\Message\YourMessage': async
|
||||
|
84
docs/source/development/export-sequence.puml
Normal file
84
docs/source/development/export-sequence.puml
Normal file
@ -0,0 +1,84 @@
|
||||
@startuml
|
||||
'https://plantuml.com/sequence-diagram
|
||||
|
||||
autonumber
|
||||
|
||||
User -> ExportController: configure export using form
|
||||
activate ExportController
|
||||
ExportController -> ExportForm: build form
|
||||
activate ExportForm
|
||||
|
||||
loop for every ExportElement (Filter, Aggregator)
|
||||
ExportForm -> ExportElement: `buildForm`
|
||||
activate ExportElement
|
||||
ExportElement -> ExportForm: add form to builders
|
||||
deactivate ExportElement
|
||||
end
|
||||
|
||||
ExportForm -> ExportController
|
||||
deactivate ExportForm
|
||||
|
||||
ExportController -> User: show form
|
||||
deactivate ExportController
|
||||
|
||||
note left of User: Configure the export:\ncheck filters, aggregators, …
|
||||
|
||||
User -> ExportController: post configuration of the export
|
||||
activate ExportController
|
||||
|
||||
ExportController -> ExportForm: `getData`
|
||||
activate ExportForm
|
||||
ExportForm -> ExportController: return data: list of entities, etc.
|
||||
deactivate ExportForm
|
||||
|
||||
loop for every ExportElement (Filter, Aggregator)
|
||||
ExportController -> ExportElement: serializeData (data)
|
||||
activate ExportElement
|
||||
ExportElement -> ExportController: return serializedData (simple array with string, int, …)
|
||||
deactivate ExportElement
|
||||
end
|
||||
|
||||
ExportController -> Database: `INSERT INTO RequestGeneration_table` (insert new entity)
|
||||
ExportController -> MessageQueue: warn about a new request
|
||||
activate MessageQueue
|
||||
ExportController -> User: "ok, generation is in process"
|
||||
deactivate ExportController
|
||||
|
||||
note left of User: The user see a waiting screen
|
||||
|
||||
MessageQueue -> MessengerConsumer: forward the message to the MessengerConsumer
|
||||
deactivate MessageQueue
|
||||
activate MessengerConsumer
|
||||
MessengerConsumer -> Database: `SELECT * FROM RequestGeneration_table WHERE id = %s`
|
||||
activate Database
|
||||
Database -> MessengerConsumer: return RequestGeneration with serializedData
|
||||
deactivate Database
|
||||
|
||||
loop for every ExportElement (Filter, Aggregator)
|
||||
MessengerConsumer -> ExportElement: deserializeData
|
||||
activate ExportElement
|
||||
ExportElement -> MessengerConsumer: return data (list of entities, etc.) from the serialized array
|
||||
deactivate ExportElement
|
||||
MessengerConsumer -> ExportElement: alter the sql query (`ExportElement::alterQuery`)
|
||||
activate ExportElement
|
||||
ExportElement -> MessengerConsumer: return the query with WHERE and GROUP BY clauses
|
||||
deactivate ExportElement
|
||||
end
|
||||
|
||||
MessengerConsumer -> MessengerConsumer: prepare the export
|
||||
MessengerConsumer -> MessengerConsumer: save the export as a stored object
|
||||
MessengerConsumer -> Database: `UPDATE RequestGeneration_table SET ready = true`
|
||||
deactivate MessengerConsumer
|
||||
|
||||
User -> ExportController: pull every 5s to know if the export is generated
|
||||
activate ExportController
|
||||
ExportController -> User: warn the export is generated
|
||||
deactivate ExportController
|
||||
|
||||
User -> ExportController: download the export from object storage
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@enduml
|
@ -11,6 +11,7 @@
|
||||
"@hotwired/stimulus": "^3.0.0",
|
||||
"@luminateone/eslint-baseline": "^1.0.9",
|
||||
"@symfony/stimulus-bridge": "^3.2.0",
|
||||
"@symfony/ux-translator": "file:vendor/symfony/ux-translator/assets",
|
||||
"@symfony/webpack-encore": "^4.1.0",
|
||||
"@tsconfig/node20": "^20.1.4",
|
||||
"@types/dompurify": "^3.0.5",
|
||||
|
@ -11,22 +11,26 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Controller;
|
||||
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Chill\MainBundle\Export\DirectExportInterface;
|
||||
use Chill\MainBundle\Export\ExportFormHelper;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\ExportManager;
|
||||
use Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessage;
|
||||
use Chill\MainBundle\Form\SavedExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\FormatterType;
|
||||
use Chill\MainBundle\Form\Type\Export\PickCenterType;
|
||||
use Chill\MainBundle\Messenger\Stamp\AuthenticationStamp;
|
||||
use Chill\MainBundle\Redis\ChillRedis;
|
||||
use Chill\MainBundle\Repository\SavedExportRepositoryInterface;
|
||||
use Chill\MainBundle\Security\Authorization\SavedExportVoter;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Clock\ClockInterface;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FormType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
@ -37,6 +41,8 @@ use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpFoundation\Session\SessionInterface;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\MessageBusInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
use Symfony\Component\Security\Core\Security;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
@ -61,6 +67,8 @@ class ExportController extends AbstractController
|
||||
private readonly SavedExportRepositoryInterface $savedExportRepository,
|
||||
private readonly Security $security,
|
||||
ParameterBagInterface $parameterBag,
|
||||
private readonly MessageBusInterface $messageBus,
|
||||
private readonly ClockInterface $clock,
|
||||
) {
|
||||
$this->filterStatsByCenters = $parameterBag->get('chill_main')['acl']['filter_stats_by_center'];
|
||||
}
|
||||
@ -128,22 +136,21 @@ class ExportController extends AbstractController
|
||||
* @throws \RedisException
|
||||
*/
|
||||
#[Route(path: '/{_locale}/exports/generate-from-saved/{id}', name: 'chill_main_export_generate_from_saved')]
|
||||
public function generateFromSavedExport(SavedExport $savedExport): RedirectResponse
|
||||
public function generateFromSavedExport(SavedExport $savedExport): Response
|
||||
{
|
||||
$this->denyAccessUnlessGranted(SavedExportVoter::GENERATE, $savedExport);
|
||||
|
||||
$key = md5(uniqid((string) random_int(0, mt_getrandmax()), false));
|
||||
$exportGeneration = ExportGeneration::fromSavedExport($savedExport, $this->clock->now()->add(new \DateInterval('P3M')));
|
||||
$this->entityManager->persist($exportGeneration);
|
||||
$this->entityManager->flush();
|
||||
|
||||
$this->redis->setEx($key, 3600, \serialize($savedExport->getOptions()));
|
||||
$this->messageBus->dispatch(
|
||||
new Envelope(
|
||||
new ExportRequestGenerationMessage($exportGeneration),
|
||||
[new AuthenticationStamp($this->security->getUser())]
|
||||
));
|
||||
|
||||
return $this->redirectToRoute(
|
||||
'chill_main_export_download',
|
||||
[
|
||||
'alias' => $savedExport->getExportAlias(),
|
||||
'key' => $key, 'prevent_save' => true,
|
||||
'returnPath' => $this->generateUrl('chill_main_export_saved_list_my'),
|
||||
]
|
||||
);
|
||||
return new Response('Ok: '.$exportGeneration->getId()->toString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -28,8 +28,10 @@ interface DirectExportInterface extends ExportElementInterface
|
||||
|
||||
/**
|
||||
* Generate the export.
|
||||
*
|
||||
* @return FormattedExportGeneration
|
||||
*/
|
||||
public function generate(array $acl, array $data = []): Response;
|
||||
public function generate(array $acl, array $data = []): Response|FormattedExportGeneration;
|
||||
|
||||
/**
|
||||
* get a description, which will be used in UI (and translated).
|
||||
|
@ -11,7 +11,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\SavedExport;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Form\Type\Export\ExportType;
|
||||
use Chill\MainBundle\Form\Type\Export\FilterType;
|
||||
use Chill\MainBundle\Form\Type\Export\FormatterType;
|
||||
@ -91,7 +91,7 @@ final readonly class ExportFormHelper
|
||||
}
|
||||
|
||||
public function savedExportDataToFormData(
|
||||
SavedExport $savedExport,
|
||||
ExportGeneration $savedExport,
|
||||
string $step,
|
||||
array $formOptions = [],
|
||||
): array {
|
||||
@ -104,7 +104,7 @@ final readonly class ExportFormHelper
|
||||
}
|
||||
|
||||
private function savedExportDataToFormDataStepCenter(
|
||||
SavedExport $savedExport,
|
||||
ExportGeneration $savedExport,
|
||||
): array {
|
||||
$builder = $this->formFactory
|
||||
->createBuilder(
|
||||
@ -125,7 +125,7 @@ final readonly class ExportFormHelper
|
||||
}
|
||||
|
||||
private function savedExportDataToFormDataStepExport(
|
||||
SavedExport $savedExport,
|
||||
ExportGeneration $savedExport,
|
||||
array $formOptions,
|
||||
): array {
|
||||
$builder = $this->formFactory
|
||||
@ -147,7 +147,7 @@ final readonly class ExportFormHelper
|
||||
}
|
||||
|
||||
private function savedExportDataToFormDataStepFormatter(
|
||||
SavedExport $savedExport,
|
||||
ExportGeneration $savedExport,
|
||||
array $formOptions,
|
||||
): array {
|
||||
$builder = $this->formFactory
|
||||
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Chill\MainBundle\Entity\User;
|
||||
|
||||
class ExportGenerationContext
|
||||
{
|
||||
public function __construct(
|
||||
public readonly User $byUser,
|
||||
) {}
|
||||
}
|
52
src/Bundle/ChillMainBundle/Export/ExportGenerator.php
Normal file
52
src/Bundle/ChillMainBundle/Export/ExportGenerator.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
use Chill\DocStoreBundle\Entity\StoredObject;
|
||||
use Chill\DocStoreBundle\Service\StoredObjectManagerInterface;
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Doctrine\DBAL\LockMode;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
final readonly class ExportGenerator
|
||||
{
|
||||
public function __construct(
|
||||
private ExportManager $exportManager,
|
||||
private StoredObjectManagerInterface $storedObjectManager,
|
||||
private EntityManagerInterface $entityManager,
|
||||
private ExportFormHelper $exportFormHelper,
|
||||
) {}
|
||||
|
||||
public function generate(ExportGeneration $exportGeneration, User $user): void
|
||||
{
|
||||
$this->entityManager->wrapInTransaction(function () use ($exportGeneration) {
|
||||
$object = $exportGeneration->getStoredObject();
|
||||
$this->entityManager->refresh($exportGeneration, LockMode::PESSIMISTIC_WRITE);
|
||||
$this->entityManager->refresh($object, LockMode::PESSIMISTIC_WRITE);
|
||||
|
||||
if (StoredObject::STATUS_PENDING !== $object->getStatus()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$generation = $this->exportManager->generateExport(
|
||||
$exportGeneration->getExportAlias(),
|
||||
$centers = $this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'centers'),
|
||||
$this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'export', ['picked_centers' => $centers]),
|
||||
$this->exportFormHelper->savedExportDataToFormData($exportGeneration, 'formatter', ['picked_centers' => $centers]),
|
||||
$user,
|
||||
);
|
||||
|
||||
$this->storedObjectManager->write($exportGeneration->getStoredObject(), $generation->content, $generation->contentType);
|
||||
});
|
||||
}
|
||||
}
|
@ -165,25 +165,31 @@ class ExportManager
|
||||
$this->formatters[$alias] = $formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response which contains the requested data.
|
||||
*/
|
||||
public function generate(string $exportAlias, array $pickedCentersData, array $data, array $formatterData): Response
|
||||
public function generateExport(string $exportAlias, array $pickedCentersData, array $data, array $formatterData, User $byUser): FormattedExportGeneration
|
||||
{
|
||||
$export = $this->getExport($exportAlias);
|
||||
$centers = $this->getPickedCenters($pickedCentersData);
|
||||
$context = new ExportGenerationContext($byUser);
|
||||
|
||||
if ($export instanceof DirectExportInterface) {
|
||||
return $export->generate(
|
||||
$generatedExport = $export->generate(
|
||||
$this->buildCenterReachableScopes($centers, $export),
|
||||
$data[ExportType::EXPORT_KEY]
|
||||
$data[ExportType::EXPORT_KEY],
|
||||
);
|
||||
|
||||
if ($generatedExport instanceof Response) {
|
||||
trigger_deprecation('chill-project/chill-bundles', '3.10', 'DirectExportInterface should not return a %s instance, but a %s instance', Response::class, FormattedExportGeneration::class);
|
||||
|
||||
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('Content-Type'));
|
||||
}
|
||||
|
||||
return $generatedExport;
|
||||
}
|
||||
|
||||
$query = $export->initiateQuery(
|
||||
$this->retrieveUsedModifiers($data),
|
||||
$this->buildCenterReachableScopes($centers, $export),
|
||||
$data[ExportType::EXPORT_KEY]
|
||||
$export->denormalizeFormData($data[ExportType::EXPORT_KEY], $context),
|
||||
);
|
||||
|
||||
if ($query instanceof \Doctrine\ORM\NativeQuery) {
|
||||
@ -194,10 +200,10 @@ class ExportManager
|
||||
}
|
||||
} elseif ($query instanceof QueryBuilder) {
|
||||
// handle filters
|
||||
$this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers);
|
||||
$this->handleFilters($export, $query, $data[ExportType::FILTER_KEY], $centers, $context);
|
||||
|
||||
// handle aggregators
|
||||
$this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers);
|
||||
$this->handleAggregators($export, $query, $data[ExportType::AGGREGATOR_KEY], $centers, $context);
|
||||
|
||||
$this->logger->notice('[export] will execute this qb in export', [
|
||||
'dql' => $query->getDQL(),
|
||||
@ -206,7 +212,7 @@ class ExportManager
|
||||
throw new \UnexpectedValueException('The method `intiateQuery` should return a `\Doctrine\ORM\NativeQuery` or a `Doctrine\ORM\QueryBuilder` object.');
|
||||
}
|
||||
|
||||
$result = $export->getResult($query, $data[ExportType::EXPORT_KEY]);
|
||||
$result = $export->getResult($query, $export->denormalizeFormData($data[ExportType::EXPORT_KEY], $context));
|
||||
|
||||
if (!is_iterable($result)) {
|
||||
throw new \UnexpectedValueException(sprintf('The result of the export should be an iterable, %s given', \gettype($result)));
|
||||
@ -231,14 +237,44 @@ class ExportManager
|
||||
$filtersData[$alias] = $data[ExportType::FILTER_KEY][$alias]['form'];
|
||||
}
|
||||
|
||||
return $formatter->getResponse(
|
||||
if (method_exists($formatter, 'generate')) {
|
||||
return $formatter->generate(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
$data[ExportType::EXPORT_KEY],
|
||||
$filtersData,
|
||||
$aggregatorsData,
|
||||
);
|
||||
}
|
||||
|
||||
trigger_deprecation('chill-project/chill-bundles', '3.10', '%s should implements the "generate" method', FormatterInterface::class);
|
||||
|
||||
$generatedExport = $formatter->getResponse(
|
||||
$result,
|
||||
$formatterData,
|
||||
$exportAlias,
|
||||
$data[ExportType::EXPORT_KEY],
|
||||
$filtersData,
|
||||
$aggregatorsData
|
||||
$aggregatorsData,
|
||||
);
|
||||
|
||||
return new FormattedExportGeneration($generatedExport->getContent(), $generatedExport->headers->get('content-type'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a response which contains the requested data.
|
||||
*/
|
||||
public function generate(string $exportAlias, array $pickedCentersData, array $data, array $formatterData): Response
|
||||
{
|
||||
$generated = $this->generateExport(
|
||||
$exportAlias,
|
||||
$pickedCentersData,
|
||||
$data,
|
||||
$formatterData,
|
||||
);
|
||||
|
||||
return new Response($generated->content, headers: ['Content-Type' => $generated->contentType]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -453,6 +489,7 @@ class ExportManager
|
||||
DirectExportInterface|ExportInterface|null $export = null,
|
||||
?array $centers = null,
|
||||
): bool {
|
||||
dump(__METHOD__, $this->tokenStorage->getToken()->getUser());
|
||||
if ($element instanceof ExportInterface || $element instanceof DirectExportInterface) {
|
||||
$role = $element->requiredRole();
|
||||
} else {
|
||||
@ -473,7 +510,7 @@ class ExportManager
|
||||
$role
|
||||
);
|
||||
}
|
||||
|
||||
dump($centers);
|
||||
foreach ($centers as $center) {
|
||||
if (false === $this->authorizationChecker->isGranted($role, $center)) {
|
||||
// debugging
|
||||
@ -534,16 +571,13 @@ class ExportManager
|
||||
QueryBuilder $qb,
|
||||
array $data,
|
||||
array $center,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$aggregators = $this->retrieveUsedAggregators($data);
|
||||
|
||||
foreach ($aggregators as $alias => $aggregator) {
|
||||
if (false === $this->isGrantedForElement($aggregator, $export, $center)) {
|
||||
throw new UnauthorizedHttpException('You are not authorized to use the aggregator'.$aggregator->getTitle());
|
||||
}
|
||||
|
||||
$formData = $data[$alias];
|
||||
$aggregator->alterQuery($qb, $formData['form']);
|
||||
$aggregator->alterQuery($qb, $aggregator->denormalizeFormData($formData['form'], $context));
|
||||
}
|
||||
}
|
||||
|
||||
@ -561,20 +595,17 @@ class ExportManager
|
||||
QueryBuilder $qb,
|
||||
mixed $data,
|
||||
array $centers,
|
||||
ExportGenerationContext $context,
|
||||
) {
|
||||
$filters = $this->retrieveUsedFilters($data);
|
||||
|
||||
foreach ($filters as $alias => $filter) {
|
||||
if (false === $this->isGrantedForElement($filter, $export, $centers)) {
|
||||
throw new UnauthorizedHttpException('You are not authorized to use the filter '.$filter->getTitle());
|
||||
}
|
||||
|
||||
$formData = $data[$alias];
|
||||
|
||||
$this->logger->debug('alter query by filter '.$alias, [
|
||||
'class' => self::class, 'function' => __FUNCTION__,
|
||||
]);
|
||||
$filter->alterQuery($qb, $formData['form']);
|
||||
$filter->alterQuery($qb, $filter->denormalizeFormData($formData['form'], $context));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export;
|
||||
|
||||
final readonly class FormattedExportGeneration
|
||||
{
|
||||
public function __construct(
|
||||
public string $content,
|
||||
public string $contentType,
|
||||
) {}
|
||||
}
|
@ -13,6 +13,9 @@ namespace Chill\MainBundle\Export;
|
||||
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
|
||||
/**
|
||||
* @method generate($result, $formatterData, string $exportAlias, array $exportData, array $filtersData, array $aggregatorsData): FormattedExportGeneration
|
||||
*/
|
||||
interface FormatterInterface
|
||||
{
|
||||
public const TYPE_LIST = 'list';
|
||||
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Messenger;
|
||||
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Chill\MainBundle\Entity\User;
|
||||
use Ramsey\Uuid\UuidInterface;
|
||||
|
||||
final readonly class ExportRequestGenerationMessage
|
||||
{
|
||||
public UuidInterface $id;
|
||||
|
||||
public int $userId;
|
||||
|
||||
public function __construct(
|
||||
ExportGeneration $exportGeneration,
|
||||
User $user,
|
||||
) {
|
||||
$this->id = $exportGeneration->getId();
|
||||
$this->userId = $user->getId();
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Export\Messenger;
|
||||
|
||||
use Chill\MainBundle\Export\ExportGenerator;
|
||||
use Chill\MainBundle\Repository\ExportGenerationRepository;
|
||||
use Chill\MainBundle\Repository\UserRepositoryInterface;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Symfony\Component\Messenger\Handler\MessageHandlerInterface;
|
||||
|
||||
#[AsMessageHandler]
|
||||
final readonly class ExportRequestGenerationMessageHandler implements MessageHandlerInterface
|
||||
{
|
||||
public function __construct(
|
||||
private ExportGenerationRepository $repository,
|
||||
private UserRepositoryInterface $userRepository,
|
||||
private ExportGenerator $exportGenerator,
|
||||
) {}
|
||||
|
||||
public function __invoke(ExportRequestGenerationMessage $exportRequestGenerationMessage)
|
||||
{
|
||||
if (null === $exportGeneration = $this->repository->find($exportRequestGenerationMessage->id)) {
|
||||
throw new \UnexpectedValueException('ExportRequestGenerationMessage not found');
|
||||
}
|
||||
|
||||
if (null === $user = $this->userRepository->find($exportRequestGenerationMessage->userId)) {
|
||||
throw new \UnexpectedValueException('User not found');
|
||||
}
|
||||
|
||||
$this->exportGenerator->generate($exportGeneration, $user);
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace Chill\MainBundle\Messenger\Authentication;
|
||||
|
||||
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
class AuthenticatedMessengerToken extends AbstractToken
|
||||
{
|
||||
public function __construct(UserInterface $user, array $roles = [])
|
||||
{
|
||||
parent::__construct($roles);
|
||||
$this->setUser($user);
|
||||
$this->setAuthenticated(true);
|
||||
}
|
||||
|
||||
public function getCredentials(): null
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Messenger\Middleware;
|
||||
|
||||
use Chill\MainBundle\Messenger\Authentication\AuthenticatedMessengerToken;
|
||||
use Chill\MainBundle\Messenger\Stamp\AuthenticationStamp;
|
||||
use Symfony\Component\Messenger\Envelope;
|
||||
use Symfony\Component\Messenger\Middleware\MiddlewareInterface;
|
||||
use Symfony\Component\Messenger\Middleware\StackInterface;
|
||||
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
|
||||
use Symfony\Component\Security\Core\User\UserProviderInterface;
|
||||
|
||||
final readonly class AuthenticationMiddleware implements MiddlewareInterface
|
||||
{
|
||||
public function __construct(
|
||||
private TokenStorageInterface $tokenStorage,
|
||||
private UserProviderInterface $userProvider,
|
||||
) {}
|
||||
|
||||
public function handle(Envelope $envelope, StackInterface $stack): Envelope
|
||||
{
|
||||
dump(__METHOD__);
|
||||
if (null !== $authenticationStamp = $envelope->last(AuthenticationStamp::class)) {
|
||||
return;
|
||||
/** @var AuthenticationStamp $authenticationStamp */
|
||||
dump("authenticate user", $authenticationStamp->getUserId());
|
||||
if (null !== $this->tokenStorage->getToken()) {
|
||||
dump("token already present");
|
||||
|
||||
} else {
|
||||
$user = $this->userProvider->loadUserByUsername($authenticationStamp->getUserId());
|
||||
$this->tokenStorage->setToken(new AuthenticatedMessengerToken($user, [...$user->getRoles(), 'IS_AUTHENTICATED_FULLY']));
|
||||
}
|
||||
}
|
||||
|
||||
return $stack->next()->handle($envelope, $stack);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Messenger\Stamp;
|
||||
|
||||
use Symfony\Component\Messenger\Stamp\StampInterface;
|
||||
use Symfony\Component\Security\Core\User\UserInterface;
|
||||
|
||||
/**
|
||||
* Stamp which will add a user as authenticated during the message handling.
|
||||
*/
|
||||
final readonly class AuthenticationStamp implements StampInterface
|
||||
{
|
||||
private string $userId;
|
||||
|
||||
public function __construct(UserInterface $user)
|
||||
{
|
||||
$this->userId = $user->getUserIdentifier();
|
||||
}
|
||||
|
||||
public function getUserId(): string
|
||||
{
|
||||
return $this->userId;
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* Chill is a software for social workers
|
||||
*
|
||||
* For the full copyright and license information, please view
|
||||
* the LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Chill\MainBundle\Repository;
|
||||
|
||||
use Chill\MainBundle\Entity\ExportGeneration;
|
||||
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
|
||||
use Doctrine\Persistence\ManagerRegistry;
|
||||
|
||||
/**
|
||||
* @extends ServiceEntityRepository<ExportGeneration>
|
||||
*/
|
||||
class ExportGenerationRepository extends ServiceEntityRepository
|
||||
{
|
||||
public function __construct(ManagerRegistry $registry)
|
||||
{
|
||||
parent::__construct($registry, ExportGeneration::class);
|
||||
}
|
||||
}
|
@ -101,6 +101,11 @@ services:
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Messenger\:
|
||||
resource: '../Messenger/'
|
||||
autowire: true
|
||||
autoconfigure: true
|
||||
|
||||
Chill\MainBundle\Cron\:
|
||||
resource: '../Cron'
|
||||
autowire: true
|
||||
|
@ -6,8 +6,12 @@ services:
|
||||
Chill\MainBundle\Export\Helper\:
|
||||
resource: '../../Export/Helper'
|
||||
|
||||
Chill\MainBundle\Export\Messenger\ExportRequestGenerationMessageHandler: ~
|
||||
|
||||
Chill\MainBundle\Export\ExportFormHelper: ~
|
||||
|
||||
Chill\MainBundle\Export\ExportGenerator: ~
|
||||
|
||||
chill.main.export_element_validator:
|
||||
class: Chill\MainBundle\Validator\Constraints\Export\ExportElementConstraintValidator
|
||||
tags:
|
||||
|
@ -12,6 +12,7 @@ declare(strict_types=1);
|
||||
namespace Chill\PersonBundle\Export\Export;
|
||||
|
||||
use Chill\MainBundle\Export\AccompanyingCourseExportHelper;
|
||||
use Chill\MainBundle\Export\ExportGenerationContext;
|
||||
use Chill\MainBundle\Export\ExportInterface;
|
||||
use Chill\MainBundle\Export\FormatterInterface;
|
||||
use Chill\MainBundle\Export\GroupedExportInterface;
|
||||
@ -49,6 +50,16 @@ class CountAccompanyingCourse implements ExportInterface, GroupedExportInterface
|
||||
return [];
|
||||
}
|
||||
|
||||
public function normalizeFormData(array $formData): array
|
||||
{
|
||||
return $formData;
|
||||
}
|
||||
|
||||
public function denormalizeFormData(array $formData, ExportGenerationContext $context): array
|
||||
{
|
||||
return $formData;
|
||||
}
|
||||
|
||||
public function getAllowedFormattersTypes(): array
|
||||
{
|
||||
return [FormatterInterface::TYPE_TABULAR];
|
||||
|
Loading…
x
Reference in New Issue
Block a user