Browse Source

setup new public events view and override schema:update command

Olivier Massot 3 years ago
parent
commit
fe765c792e

+ 0 - 4
bin/console

@@ -38,10 +38,6 @@ if ($_SERVER['APP_DEBUG']) {
     }
 }
 
-// Prevent doctrine:schema:update to be executed during the progressive migration
-if (in_array($input->getFirstArgument(), ["d:s:u", "doctrine:schema:update"])) {
-    throw new \InvalidArgumentException("<!> schema:update should not be used here for the time of the progressive migration");
-}
 $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
 $application = new Application($kernel);
 $application->run($input);

+ 0 - 0
sql/schema-extension/001-view_portail_events.sql


+ 32 - 0
sql/schema-extensions/001-view_portail_events.sql

@@ -0,0 +1,32 @@
+CREATE OR REPLACE VIEW view_public_events
+AS
+    SELECT
+        b.uuid, b.organization_id AS organizationId, b.name, b.description, b.url, b.datetimeStart, b.datetimeEnd,
+        p.name AS placeName, p.description AS placeDescription, p.floorSize AS placeFloorSize, p.capacity AS placeCapacity,
+        ap.addressCity AS city, ap.postalCode,
+        TRIM(BOTH ' ' FROM CONCAT( if(ap.streetAddress is null,'',ap.streetAddress), ' ', if(ap.streetAddressSecond is null,'',ap.streetAddressSecond), ' ', if(ap.streetAddressThird is null,'',ap.streetAddressThird))) AS streetAddress,
+        ap.longitude, ap.latitude,
+        r.name AS roomName, r.description AS roomDescription, r.localisation AS roomLocalisation, r.capacity AS roomCapacity, r.floorSize AS roomFloorSize, b.image_id AS imageId,
+        (SELECT CONCAT('[',GROUP_CONCAT(CONCAT(f.code)),']')
+         FROM event_categories AS ec
+            LEFT JOIN Categories AS cs ON(cs.id = ec.categories_id)
+            LEFT JOIN Familly AS f ON(f.id = cs.familly_id)
+         WHERE ec.event_id = b.id
+        ) AS categories, 'opentalent' as origin, b.id as entityId
+    FROM Booking AS b
+             INNER JOIN Organization o ON o.id = b.organization_id
+             INNER JOIN Parameters par ON par.id = o.parameters_id
+             LEFT JOIN Place AS p ON (p.id = b.place_id)
+             LEFT JOIN AddressPostal AS ap ON (ap.id = p.addressPostal_id)
+             LEFT JOIN Room AS r ON (r.id = b.room_id)
+    WHERE b.discr = 'event' AND b.datetimeEnd >= NOW() AND b.visibility = 'PUBLIC_VISIBILITY' AND b.isCanceled = 0
+UNION
+    SELECT
+        aw.uuid, null AS organizationId, aw.name, aw.description, NULL AS url, aw.datetimeStart, aw.datetimeEnd,
+        aw.place AS placeName, NULL AS placeDescription, NULL AS placeFloorSize, NULL AS placeCapacity,
+        aw.city, aw.postalCode, aw.streetAddress, aw.longitude, aw.latitude,
+        NULL AS roomName, NULL AS roomDescription, NULL AS roomLocalisation, NULL AS roomCapacity, NULL AS roomFloorSize,
+        aw.largeimage AS imageId, aw.categories AS categories, 'awin' as origin, aw.id as entityId
+    FROM  AwinProduct as aw
+    WHERE
+        aw.datetimeEnd >= NOW() AND aw.datetimeStart IS NOT NULL;

+ 1 - 0
sql/schema-extensions/readme.md

@@ -0,0 +1 @@
+The SQL scripts contained in that folder will be executed each time a doctrine:schema:update command is executed.

+ 40 - 0
src/Commands/Doctrine/SchemaUpdateCommand.php

@@ -0,0 +1,40 @@
+<?php /** @noinspection PhpUnused */
+
+namespace App\Commands\Doctrine;
+
+use App\Service\Utils\Path;
+use Doctrine\Bundle\DoctrineBundle\Command\Proxy\UpdateSchemaDoctrineCommand;
+use Doctrine\ORM\Tools\SchemaTool;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+
+/**
+ * Overrides the default doctrine:schema:update command
+ */
+class SchemaUpdateCommand extends UpdateSchemaDoctrineCommand
+{
+    protected function executeSchemaCommand(InputInterface $input, OutputInterface $output, SchemaTool $schemaTool, array $metadatas, SymfonyStyle $ui)
+    {
+        $output->writeln('-- Executing pre-update scripts');
+
+        // Lists schema extensions scripts in the '/sql/schema-extensions' dir
+        $schemaExtensionsDir = Path::join(Path::getProjectDir(), 'sql', 'schema-extensions');
+        $scripts = Path::list($schemaExtensionsDir, '*.sql');
+        sort($scripts);
+
+        // Execute those scripts in alphabetical order
+        $em = $this->getEntityManager($input);
+        $conn = $em->getConnection();
+
+        foreach ($scripts as $script) {
+            $sql = Path::read($script);
+            $conn->executeQuery($sql);
+        }
+
+        $output->writeln(sprintf('-- Database successfully updated, %s scripts executed', count($scripts)));
+
+        throw new \RuntimeException('<!> Operation interrupted: use the d:s:u command on the current version to update the DB tables');
+//        parent::executeSchemaCommand($input, $output, $schemaTool, $metadatas, $ui);
+    }
+}

+ 37 - 7
src/Commands/PostUpgrade/V0_2/PostUpgradeCommand.php

@@ -31,18 +31,14 @@ class PostUpgradeCommand extends Command
     {
         $this->logger->info('Run post-upgrade scripts for version ' . self::TARGETED_VERSION);
 
-        $this->populateSubdomains();
+//        $this->populateSubdomains();
+        $this->genEventsUuid();
 
         $output->writeln("Post-upgrade operations successfully executed");
         return Command::SUCCESS;
     }
 
-    /**
-     * Populate the new Subdomain table
-     * @throws \Exception
-     */
-    public function populateSubdomains() {
-
+    private function getOpentalentConnexion() {
         $dbUrl = $_ENV['DATABASE_URL'];
         $matches = [];
         preg_match(
@@ -58,6 +54,16 @@ class PostUpgradeCommand extends Command
             $dbPwd,
             array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8'));
         $opentalentCnn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+        return $opentalentCnn;
+    }
+
+    /**
+     * Populate the new Subdomain table
+     * @throws \Exception
+     */
+    private function populateSubdomains(): void
+    {
+        $opentalentCnn = $this->getOpentalentConnexion();
 
         $opentalentCnn->beginTransaction();
 
@@ -146,4 +152,28 @@ class PostUpgradeCommand extends Command
             throw $e;
         }
     }
+
+    private function genEventsUuid() {
+        $opentalentCnn = $this->getOpentalentConnexion();
+
+        $opentalentCnn->beginTransaction();
+
+        try {
+            $this->logger->info('Generate events uuids');
+
+            // Generating Uuid1 with native mysql function
+            $sql = "update opentalent.Booking set uuid = UUID();";
+            $opentalentCnn->query($sql);
+
+            $sql = "update opentalent.AwinProduct set uuid = UUID();";
+            $opentalentCnn->query($sql);
+
+            $opentalentCnn->commit();
+            $this->logger->info('Events uuid were successfully generated');
+        } catch (\Exception $e) {
+            $opentalentCnn->rollBack();
+            $this->logger->critical('Error while running the post-upgrade script, abort and rollback');
+            throw $e;
+        }
+    }
 }

+ 94 - 0
src/Commands/TestCommand.php

@@ -0,0 +1,94 @@
+<?php
+
+namespace App\Commands;
+
+use App\Service\Dolibarr\DolibarrSyncService;
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Command\LockableTrait;
+use Symfony\Component\Console\Helper\ProgressBar;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+
+#[AsCommand(
+    name: 'ot:dolibarr-sync',
+    description: 'Push the latest data from the Opentalent DB to dolibarr'
+)]
+class DolibarrSyncCommand extends Command
+{
+    use LockableTrait;
+
+    /**
+     * How many operations are shown each time the preview choice is made
+     */
+    const PREVIEW_CHUNK = 20;
+
+    public function __construct(
+        private DolibarrSyncService $dolibarrSyncService
+    ) {
+        parent::__construct();
+    }
+
+    protected function configure()
+    {
+        $this->addOption(
+            'preview',
+            'p',
+            InputOption::VALUE_NONE,
+            'Only preview the sync operations instead of executing it'
+        );
+    }
+
+    protected function execute(InputInterface $input, OutputInterface $output): int
+    {
+        if (!$this->lock()) {
+            $output->writeln('The command is already running in another process.');
+            return Command::SUCCESS;
+        }
+
+        $output->writeln("Start the synchronization");
+        $t0 = microtime(true);
+        $output->writeln("Scanning...");
+
+        $progressBar = new ProgressBar($output, 0);
+        $progressCallback = function($i, $total) use ($progressBar) {
+            if (!$progressBar->getMaxSteps() !== $total) {
+                $progressBar->setMaxSteps($total);
+            }
+            $progressBar->setProgress($i);
+        };
+
+        $operations = $this->dolibarrSyncService->scan($progressCallback);
+
+        $t1 = microtime(true);
+        $output->writeln("Scan lasted " . ($t1 - $t0) . " sec.");
+
+        $output->writeln(count($operations) . " operations to be executed");
+
+        if ($input->getOption('preview')) {
+            $output->writeln("-- Preview --");
+            foreach ($operations as $i => $iValue) {
+                $output->writeln($i . '. ' . $iValue->getLabel());
+                foreach ($iValue->getChangeLog() as $message) {
+                    $output->writeln('   ' . $message);
+                }
+            }
+        } else {
+            $t0 = microtime(true);
+            $output->writeln("Executing...");
+
+            $operations = $this->dolibarrSyncService->execute($operations, $progressCallback);
+
+            $successes = count(array_filter($operations, function ($o) { return $o->getStatus() === $o::STATUS_DONE; } ));
+            $errors = count(array_filter($operations, function ($o) { return $o->getStatus() === $o::STATUS_ERROR; } ));
+            $output->writeln($successes . " operations successfully executed");
+            $output->writeln($errors . " errors");
+
+            $t1 = microtime(true);
+            $output->writeln("Execution lasted " . ($t1 - $t0) . " sec.");
+        }
+
+        return Command::SUCCESS;
+    }
+}

+ 0 - 36
src/DataProvider/Public/PublicEventDataProvider.php

@@ -1,36 +0,0 @@
-<?php
-
-namespace App\DataProvider\Public;
-
-use ApiPlatform\Core\DataProvider\CollectionDataProviderInterface;
-use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
-use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
-use App\ApiResources\Public\FederationStructure;
-use App\Entity\Public\PublicEvent;
-use App\Repository\Booking\EventRepository;
-use App\Repository\Organization\OrganizationRepository;
-use Doctrine\Common\Collections\ArrayCollection;
-use Symfony\Component\HttpFoundation\RequestStack;
-
-
-class PublicEventDataProvider implements ItemDataProviderInterface, CollectionDataProviderInterface, RestrictedDataProviderInterface
-{
-    public function __construct(
-        private EventRepository $eventRepository
-    ) {}
-
-    public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
-    {
-        return PublicEvent::class === $resourceClass;
-    }
-
-    public function getItem(string $resourceClass, $id, string $operationName = null, array $context = []): ?FederationStructure
-    {
-        return $this->eventRepository->getAllEvents($id)[0];
-    }
-
-    public function getCollection(string $resourceClass, string $operationName = null): ArrayCollection
-    {
-        return new ArrayCollection($this->eventRepository->getAllEvents());
-    }
-}

+ 49 - 10
src/Entity/Public/PublicEvent.php

@@ -11,7 +11,7 @@ use App\Repository\Public\PublicEventRepository;
 use Doctrine\ORM\Mapping as ORM;
 
 #[ORM\Entity(repositoryClass: PublicEventRepository::class, readOnly: true)]
-#[ORM\Table(name: "view_portail_events")]
+#[ORM\Table(name: "view_public_events")]
 #[ApiResource(
     collectionOperations: [
         'get' => [
@@ -22,7 +22,7 @@ use Doctrine\ORM\Mapping as ORM;
     itemOperations: [
         'get' => [
             'method' => 'GET',
-            'path' => '/public/events/{id}'
+            'path' => '/public/events/{uuid}'
         ]
     ]
 )]
@@ -31,62 +31,83 @@ class PublicEvent implements ApiResourcesInterface
 {
     #[ORM\Id]
     #[ORM\Column]
-    private int $id;
+    private string $uuid;
 
+    #[ORM\Column(type: 'integer')]
     private ?int $organizationId;
 
-    #[ORM\Column()]
+    #[ORM\Column]
     private string $name;
 
+    #[ORM\Column(type: 'string')]
     private ?string $description;
 
+    #[ORM\Column]
     private ?string $url;
 
+    #[ORM\Column(type: 'datetime')]
     private \DateTime $datetimeStart;
 
+    #[ORM\Column(type: 'datetime')]
     private \DateTime $datetimeEnd;
 
+    #[ORM\Column]
     private ?string $city;
 
+    #[ORM\Column]
     private ?string $postalCode;
 
+    #[ORM\Column]
     private ?string $streetAddress;
 
+    #[ORM\Column(type: 'float')]
     private ?float $longitude;
 
+    #[ORM\Column(type: 'float')]
     private ?float $latitude;
 
+    #[ORM\Column]
     private ?string $roomName;
 
+    #[ORM\Column]
     private ?string $roomDescription;
 
+    #[ORM\Column]
     private ?string $roomLocalisation;
 
+    #[ORM\Column]
     private ?string $roomCapacity;
 
+    #[ORM\Column]
     private ?string $roomFloorSize;
 
+    #[ORM\Column(type: 'integer')]
     private ?int $imageId;
 
+    #[ORM\Column]
     private ?string $categories;
 
+    #[ORM\Column]
     private string $origin = 'opentalent';
 
+    #[ORM\Column(type: 'integer')]
+    private int $entityId;
+
     /**
-     * @return int
+     * @return string
      */
-    public function getId(): int
+    public function getUuid(): string
     {
-        return $this->id;
+        return $this->uuid;
     }
 
     /**
-     * @param int $id
+     * @param string $uuid
      * @return PublicEvent
      */
-    public function setId(int $id): self
+    public function setUuid(string $uuid): self
     {
-        $this->id = $id;
+        $this->uuid = $uuid;
         return $this;
     }
 
@@ -431,4 +452,22 @@ class PublicEvent implements ApiResourcesInterface
         $this->origin = $origin;
         return $this;
     }
+
+    /**
+     * @return int
+     */
+    public function getEntityId(): int
+    {
+        return $this->entityId;
+    }
+
+    /**
+     * @param int $entityId
+     * @return PublicEvent
+     */
+    public function setEntityId(int $entityId): self
+    {
+        $this->entityId = $entityId;
+        return $this;
+    }
 }

+ 1 - 88
src/Repository/Booking/EventRepository.php

@@ -3,101 +3,14 @@ declare(strict_types=1);
 
 namespace App\Repository\Booking;
 
-use App\Entity\Public\PublicEvent;
 use App\Entity\Booking\Event;
-use App\Enum\Public\PublicEventOriginEnum;
-use App\Service\Utils\ArrayUtils;
-use App\Service\Utils\DatesUtils;
 use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
-use Doctrine\ORM\EntityManagerInterface;
 use Doctrine\Persistence\ManagerRegistry;
 
 class EventRepository extends ServiceEntityRepository
 {
-    public function __construct(ManagerRegistry $registry, private EntityManagerInterface $em)
+    public function __construct(ManagerRegistry $registry)
     {
         parent::__construct($registry, Event::class);
     }
-
-    /**
-     * Route optimisée pour retourner les données de d'un évènement publique, sous forme d'Api resources
-     * de type PublicEvent.
-     *
-     * Cette route est utilisée par l'iframe de recherche des évènements
-     * @see https://gitlab.2iopenservice.com/opentalent/frames
-     *
-     * @return array
-     * @throws \Doctrine\DBAL\DBALException
-     * @throws \Doctrine\DBAL\Driver\Exception
-     * @throws \Doctrine\DBAL\Exception
-     */
-    public function getAllEvents(): array
-    {
-        $sql = "    SELECT
-                            b.id as id, b.organization_id AS organizationId, b.name, b.description, b.url, b.datetimeStart, b.datetimeEnd,
-                            p.name AS placeName, p.description AS placeDescription, p.floorSize AS placeFloorSize, p.capacity AS placeCapacity,
-                            ap.addressCity AS city, ap.postalCode, 
-                            TRIM(BOTH ' ' FROM CONCAT( if(ap.streetAddress is null,'',ap.streetAddress), ' ', if(ap.streetAddressSecond is null,'',ap.streetAddressSecond), ' ', if(ap.streetAddressThird is null,'',ap.streetAddressThird))) AS streetAddress, 
-                            ap.longitude, ap.latitude, 
-                            r.name AS roomName, r.description AS roomDescription, r.localisation AS roomLocalisation, r.capacity AS roomCapacity, r.floorSize AS roomFloorSize, b.image_id AS imageId, 
-                            (SELECT CONCAT('[',GROUP_CONCAT(CONCAT(f.code)),']')
-                                FROM event_categories AS ec
-                                LEFT JOIN Categories AS cs ON(cs.id = ec.categories_id)
-                                LEFT JOIN Familly AS f ON(f.id = cs.familly_id)
-                                WHERE ec.event_id = b.id
-                            ) AS categories, '" . PublicEventOriginEnum::OPENTALENT()->getValue() . "' as origin
-                    FROM Booking AS b
-                        INNER JOIN Organization o ON o.id = b.organization_id
-                        INNER JOIN Parameters par ON par.id = o.parameters_id
-                        LEFT JOIN Place AS p ON (p.id = b.place_id)
-                        LEFT JOIN AddressPostal AS ap ON (ap.id = p.addressPostal_id)
-                        LEFT JOIN Room AS r ON (r.id = b.room_id)
-                    WHERE b.discr = 'event' AND b.datetimeEnd >= NOW() AND b.visibility = 'PUBLIC_VISIBILITY' AND b.isCanceled = 0
-                UNION
-                    SELECT
-                           auto_increment_value(null) as id, null AS organizationId, aw.name, aw.description, NULL AS url, aw.datetimeStart, aw.datetimeEnd,
-                           aw.place AS placeName, NULL AS placeDescription, NULL AS placeFloorSize, NULL AS placeCapacity,
-                           aw.city, aw.postalCode, aw.streetAddress, aw.longitude, aw.latitude,
-                           NULL AS roomName, NULL AS roomDescription, NULL AS roomLocalisation, NULL AS roomCapacity, NULL AS roomFloorSize,
-                           aw.largeimage AS imageId, aw.categories AS categories, '" . PublicEventOriginEnum::AWIN()->getValue() . "' as origin
-                    FROM  AwinProduct as aw
-                    WHERE
-                     aw.datetimeEnd >= NOW() AND aw.datetimeStart IS NOT NULL;
-                ";
-
-        $stmt = $this->em->getConnection()->prepare($sql);
-        $rows = $stmt->executeQuery()->fetchAllAssociative();
-
-        return array_map('self::buildPublicEvent', $rows);
-    }
-
-    /**
-     * Build a PublicEvent with the data provided by getAllEvents() and ...
-     */
-    private static function buildPublicEvent(array $data): PublicEvent
-    {
-        return (new PublicEvent)
-            ->setId((int)$data['id'])
-            ->setOrganizationId(ArrayUtils::getAndCast($data, 'organizationId', 'int'))
-            ->setName($data['name'])
-            ->setDescription($data['description'])
-            ->setUrl($data['url'])
-            ->setDatetimeStart(DatesUtils::new($data['datetimeStart']))
-            ->setDatetimeEnd(DatesUtils::new($data['datetimeEnd']))
-            ->setCity($data['city'])
-            ->setPostalCode($data['postalCode'])
-            ->setStreetAddress($data['streetAddress'])
-            ->setLongitude(ArrayUtils::getAndCast($data, 'longitude', 'float'))
-            ->setLatitude(ArrayUtils::getAndCast($data, 'latitude', 'float'))
-            ->setRoomName($data['roomName'])
-            ->setRoomDescription($data['roomDescription'])
-            ->setRoomLocalisation($data['roomLocalisation'])
-            ->setRoomCapacity($data['roomCapacity'])
-            ->setRoomFloorSize($data['roomFloorSize'])
-            ->setImageId(ArrayUtils::getAndCast($data, 'imageId', 'int'))
-            ->setCategories($data['categories'])
-            ->setOrigin($data['origin']);
-    }
-
-
 }

+ 22 - 0
src/Repository/Public/PublicEventRepository.php

@@ -0,0 +1,22 @@
+<?php
+declare(strict_types=1);
+
+namespace App\Repository\Public;
+
+use App\Entity\Public\PublicEvent;
+use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
+use Doctrine\Persistence\ManagerRegistry;
+
+/**
+ * @method PublicEvent|null find($id, $lockMode = null, $lockVersion = null)
+ * @method PublicEvent|null findOneBy(array $criteria, array $orderBy = null)
+ * @method PublicEvent[]    findAll()
+ * @method PublicEvent[]    findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
+ */
+final class PublicEventRepository extends ServiceEntityRepository
+{
+    public function __construct(ManagerRegistry $registry)
+    {
+        parent::__construct($registry, PublicEvent::class);
+    }
+}

+ 20 - 0
src/Service/Utils/Path.php

@@ -43,4 +43,24 @@ class Path
         $paths = array_filter(func_get_args(), function ($s) { return $s !== ''; });
         return preg_replace('#/+#','/',join('/', $paths));
     }
+
+    /**
+     * List the files located in the given directory
+     *
+     * @param string $path
+     * @param string $glob
+     * @return array
+     */
+    public static function list(string $path, string $glob = '*'): array {
+        return glob(self::join($path, $glob));
+    }
+
+    public static function read(string $path): string
+    {
+        $content = file_get_contents($path);
+        if ($content === false) {
+            throw new \RuntimeException("File could not be read");
+        }
+        return $content;
+    }
 }

+ 6 - 4
src/Service/Utils/Uuid.php

@@ -5,13 +5,15 @@ namespace App\Service\Utils;
 class Uuid
 {
     /**
-     * Generate an UUID v4
+     * Generates an UUID v4
      *
-     * @var int $length Max length of the uuid
+     * @var ?int $length To limit the length of the uuid (standard uuid count 36 cars). Warning: reducing
+     *                   the number of characters breaks the warranty of global unity.
+     * @return string
      */
-    public static function uuid(int $length = 8): string
+    public static function uuid(int $length = null): string
     {
         $uuid = \Ramsey\Uuid\Uuid::uuid4()->toString();
-        return substr($uuid, 0, $length);
+        return $length !== null ? substr($uuid, 0, $length) : $uuid;
     }
 }