From 2a326dc5a89e3619d1031f9eb98a26c89c7dc228 Mon Sep 17 00:00:00 2001 From: philippe Date: Mon, 8 Sep 2025 10:17:47 +0200 Subject: [PATCH 1/3] POC --- src/Console/ConfigureCmsCommand.php | 164 ++++++++++++++------------ src/Deploy/Crawler/Observer.php | 0 src/Deploy/DeployServiceProvider.php | 0 src/Deploy/Jobs/CrawlSite.php | 0 src/Deploy/Jobs/CrawlSiteHandler.php | 0 src/Deploy/Routing/UrlGenerator.php | 0 src/Deploy/RoutingServiceProvider.php | 0 src/OzuCms/OzuCollectionConfig.php | 23 ++-- 8 files changed, 102 insertions(+), 85 deletions(-) delete mode 100644 src/Deploy/Crawler/Observer.php delete mode 100644 src/Deploy/DeployServiceProvider.php delete mode 100644 src/Deploy/Jobs/CrawlSite.php delete mode 100644 src/Deploy/Jobs/CrawlSiteHandler.php delete mode 100644 src/Deploy/Routing/UrlGenerator.php delete mode 100644 src/Deploy/RoutingServiceProvider.php diff --git a/src/Console/ConfigureCmsCommand.php b/src/Console/ConfigureCmsCommand.php index c8f0eee..5a6ced2 100644 --- a/src/Console/ConfigureCmsCommand.php +++ b/src/Console/ConfigureCmsCommand.php @@ -2,7 +2,6 @@ namespace Code16\OzuClient\Console; -use Closure; use Code16\OzuClient\Client; use Code16\OzuClient\OzuCms\Form\OzuField; use Code16\OzuClient\OzuCms\List\OzuColumn; @@ -16,8 +15,8 @@ class ConfigureCmsCommand extends Command { protected $signature = 'ozu:configure-cms'; - protected $description = 'Send CMS configuration to Ozu.'; + private Client $ozuClient; public function handle(Client $ozuClient): int { @@ -27,85 +26,96 @@ public function handle(Client $ozuClient): int return self::SUCCESS; } + $this->ozuClient = $ozuClient; + collect(config('ozu-client.collections')) - ->map(function ($collection, $k) { - $model = match (true) { - is_string($collection) => app($collection), - $collection instanceof Closure => $collection(), - default => $collection, - }; - - $collection = $model::configureOzuCollection(new OzuCollectionConfig()); - $list = $model::configureOzuCollectionList(new OzuCollectionListConfig()); - $form = $model::configureOzuCollectionForm(new OzuCollectionFormConfig()); - - return [ - 'key' => $model->ozuCollectionKey(), - 'label' => $collection->label(), - 'icon' => $collection->icon(), - 'hasPublicationState' => $collection->hasPublicationState(), - 'autoDeployDateField' => $collection->autoDeployDateField(), - 'isCreatable' => $collection->isCreatable(), - 'isDeletable' => $collection->isDeletable(), - 'order' => $k + 1, - 'list' => [ - 'isReorderable' => $list->isReorderable(), - 'isSearchable' => $list->isSearchable(), - 'isPaginated' => $list->isPaginated(), - 'defaultSort' => $list->defaultSort(), - 'belongsToFilter' => $list->belongsToFilter()?->toArray(), - 'columns' => $list - ->columns() - ->map(fn (OzuColumn $column) => [ - 'type' => $column->type(), - 'key' => $column->key(), - 'label' => $column->label(), - 'size' => $column->size(), - ]), - ], - 'form' => [ - 'title' => $form->titleField()?->toArray(), - 'cover' => $form->coverField()?->toArray(), - 'content' => $form->contentField()?->toArray(), - 'fields' => $form - ->customFields() - ->map(fn (OzuField $field) => $field->toArray()), - ], - 'customFields' => collect(Schema::getColumnListing($model->getTable())) - ->filter(fn (string $column) => !in_array($column, $model::$ozuColumns)) - ->mapWithKeys(fn (string $column) => [ - $column => match (Schema::getColumnType($model->getTable(), $column)) { - 'datetime', 'timestamps' => 'dateTime', - 'date' => 'date', - 'int', 'bigint', 'smallint', 'mediumint', 'tinyint' => 'integer', - 'float', 'double' => 'float', - 'text', 'json' => 'text', - default => 'string', - }, - ]), - ]; - }) - ->each(function (array $collection) use ($ozuClient) { - $this->info('Update CMS configuration for ['.$collection['key'].'].'); - try { - $ozuClient->updateCollectionSharpConfiguration( - $collection['key'], - $collection - ); - } catch (RequestException $e) { - if ($message = $e->response->json()) { - if (!isset($message['message'])) { - throw $e; - } - $this->error('['.$collection['key'].'] '.$message['message']); - } else { - throw $e; - } - } - }); + ->each(fn ($collection, $k) => $this->updateCmsConfigurationFor($collection, $k+1)); $this->info('CMS configuration sent to Ozu.'); return self::SUCCESS; } + + private function updateCmsConfigurationFor($collectionClass, int $order = 1, bool $menu = true): void + { + $model = match (true) { + is_string($collectionClass) => app($collectionClass), + is_callable($collectionClass) => $collectionClass(), + default => $collectionClass, + }; + + $collection = $model::configureOzuCollection(new OzuCollectionConfig()); + $list = $model::configureOzuCollectionList(new OzuCollectionListConfig()); + $form = $model::configureOzuCollectionForm(new OzuCollectionFormConfig()); + + $payload = [ + 'key' => $model->ozuCollectionKey(), + 'label' => $collection->label(), + 'icon' => $collection->icon(), + 'isMenu' => $menu, + 'hasPublicationState' => $collection->hasPublicationState(), + 'autoDeployDateField' => $collection->autoDeployDateField(), + 'isCreatable' => $collection->isCreatable(), + 'isDeletable' => $collection->isDeletable(), + 'subCollections' => $collection->subCollections(), + 'order' => $order, + 'list' => [ + 'isReorderable' => $list->isReorderable(), + 'isSearchable' => $list->isSearchable(), + 'isPaginated' => $list->isPaginated(), + 'defaultSort' => $list->defaultSort(), + 'belongsToFilter' => $list->belongsToFilter()?->toArray(), + 'columns' => $list + ->columns() + ->map(fn (OzuColumn $column) => [ + 'type' => $column->type(), + 'key' => $column->key(), + 'label' => $column->label(), + 'size' => $column->size(), + ]), + ], + 'form' => [ + 'title' => $form->titleField()?->toArray(), + 'cover' => $form->coverField()?->toArray(), + 'content' => $form->contentField()?->toArray(), + 'fields' => $form + ->customFields() + ->map(fn (OzuField $field) => $field->toArray()), + ], + 'customFields' => collect(Schema::getColumnListing($model->getTable())) + ->filter(fn (string $column) => !in_array($column, $model::$ozuColumns)) + ->mapWithKeys(fn (string $column) => [ + $column => match (Schema::getColumnType($model->getTable(), $column)) { + 'datetime', 'timestamps' => 'dateTime', + 'date' => 'date', + 'int', 'bigint', 'smallint', 'mediumint', 'tinyint' => 'integer', + 'float', 'double' => 'float', + 'text', 'json' => 'text', + default => 'string', + }, + ]), + ]; + + $this->info('Update CMS configuration for ['.$payload['key'].'].'); + + try { + $this->ozuClient->updateCollectionSharpConfiguration($payload['key'], $payload); + + $collection->subCollections() + ->keys() + ->each(fn ($subCollectionClass) => $this + ->updateCmsConfigurationFor($subCollectionClass, menu: false) + ); + + } catch (RequestException $e) { + if ($message = $e->response->json()) { + if (!isset($message['message'])) { + throw $e; + } + $this->error('['.$payload['key'].'] '.$message['message']); + } else { + throw $e; + } + } + } } diff --git a/src/Deploy/Crawler/Observer.php b/src/Deploy/Crawler/Observer.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Deploy/DeployServiceProvider.php b/src/Deploy/DeployServiceProvider.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Deploy/Jobs/CrawlSite.php b/src/Deploy/Jobs/CrawlSite.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Deploy/Jobs/CrawlSiteHandler.php b/src/Deploy/Jobs/CrawlSiteHandler.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Deploy/Routing/UrlGenerator.php b/src/Deploy/Routing/UrlGenerator.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/Deploy/RoutingServiceProvider.php b/src/Deploy/RoutingServiceProvider.php deleted file mode 100644 index e69de29..0000000 diff --git a/src/OzuCms/OzuCollectionConfig.php b/src/OzuCms/OzuCollectionConfig.php index 8f2493b..3c8f6a3 100644 --- a/src/OzuCms/OzuCollectionConfig.php +++ b/src/OzuCms/OzuCollectionConfig.php @@ -2,19 +2,17 @@ namespace Code16\OzuClient\OzuCms; +use Illuminate\Support\Collection; + class OzuCollectionConfig { protected string $label; - protected string $icon; - protected bool $hasPublicationState = false; - protected ?string $autoDeployDateField = null; - private bool $isCreatable = true; - private bool $isDeletable = true; + private array $subCollections = []; public function setLabel(string $label): self { @@ -39,9 +37,6 @@ public function setHasPublicationState(bool $hasState = true): self /** * Declare which date field will trigger auto-deploy when reached - * - * @param string|null $field - * @return $this */ public function setAutoDeployDateField(string $fieldKey): self { @@ -64,6 +59,13 @@ public function setIsDeletable(bool $isDeletable = true): self return $this; } + public function addSubCollection(string $collectionClass, string $label): self + { + $this->subCollections[$collectionClass] = $label; + + return $this; + } + public function label(): string { return $this->label ?? 'no label'; @@ -98,4 +100,9 @@ public function isDeletable(): bool { return $this->isDeletable; } + + public function subCollections(): Collection + { + return collect($this->subCollections); + } } From 1ec2194b07fb2e44ce748a4405bf426b9a74cf02 Mon Sep 17 00:00:00 2001 From: philippe Date: Wed, 10 Sep 2025 16:14:25 +0200 Subject: [PATCH 2/3] Fix --- src/Console/ConfigureCmsCommand.php | 10 +++++----- src/OzuCms/OzuCollectionConfig.php | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Console/ConfigureCmsCommand.php b/src/Console/ConfigureCmsCommand.php index 5a6ced2..e56b338 100644 --- a/src/Console/ConfigureCmsCommand.php +++ b/src/Console/ConfigureCmsCommand.php @@ -36,7 +36,7 @@ public function handle(Client $ozuClient): int return self::SUCCESS; } - private function updateCmsConfigurationFor($collectionClass, int $order = 1, bool $menu = true): void + private function updateCmsConfigurationFor($collectionClass, int $order = 1, bool $isSubCollection = false): void { $model = match (true) { is_string($collectionClass) => app($collectionClass), @@ -52,12 +52,13 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo 'key' => $model->ozuCollectionKey(), 'label' => $collection->label(), 'icon' => $collection->icon(), - 'isMenu' => $menu, + 'isMenu' => !$isSubCollection, 'hasPublicationState' => $collection->hasPublicationState(), 'autoDeployDateField' => $collection->autoDeployDateField(), 'isCreatable' => $collection->isCreatable(), 'isDeletable' => $collection->isDeletable(), - 'subCollections' => $collection->subCollections(), + 'subCollections' => $collection->subCollections() + ->map(fn ($subCollectionClass) => app($subCollectionClass)->ozuCollectionKey()), 'order' => $order, 'list' => [ 'isReorderable' => $list->isReorderable(), @@ -102,9 +103,8 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo $this->ozuClient->updateCollectionSharpConfiguration($payload['key'], $payload); $collection->subCollections() - ->keys() ->each(fn ($subCollectionClass) => $this - ->updateCmsConfigurationFor($subCollectionClass, menu: false) + ->updateCmsConfigurationFor($subCollectionClass, isSubCollection: true) ); } catch (RequestException $e) { diff --git a/src/OzuCms/OzuCollectionConfig.php b/src/OzuCms/OzuCollectionConfig.php index 3c8f6a3..26d5b21 100644 --- a/src/OzuCms/OzuCollectionConfig.php +++ b/src/OzuCms/OzuCollectionConfig.php @@ -59,9 +59,9 @@ public function setIsDeletable(bool $isDeletable = true): self return $this; } - public function addSubCollection(string $collectionClass, string $label): self + public function addSubCollection(string $collectionClass): self { - $this->subCollections[$collectionClass] = $label; + $this->subCollections[] = $collectionClass; return $this; } From 11224b66cb256e1252db471bb9f3e11337b552f1 Mon Sep 17 00:00:00 2001 From: PatrickePatate Date: Tue, 14 Oct 2025 17:26:24 +0200 Subject: [PATCH 3/3] Add tests, better error display, ... --- config/ozu-client.php | 3 +- src/Console/ConfigureCmsCommand.php | 42 ++++++++++++++-- tests/Console/ConfigureCmsCommandTest.php | 50 +++++++++++++++++++ .../Fixtures/DummySubcollectionTestModel.php | 38 ++++++++++++++ 4 files changed, 128 insertions(+), 5 deletions(-) create mode 100644 tests/Fixtures/DummySubcollectionTestModel.php diff --git a/config/ozu-client.php b/config/ozu-client.php index d966c3f..7091019 100644 --- a/config/ozu-client.php +++ b/config/ozu-client.php @@ -1,7 +1,8 @@ [ // \App\Models\Project::class, diff --git a/src/Console/ConfigureCmsCommand.php b/src/Console/ConfigureCmsCommand.php index e56b338..a21d452 100644 --- a/src/Console/ConfigureCmsCommand.php +++ b/src/Console/ConfigureCmsCommand.php @@ -15,9 +15,12 @@ class ConfigureCmsCommand extends Command { protected $signature = 'ozu:configure-cms'; - protected $description = 'Send CMS configuration to Ozu.'; + protected $aliases = ['ozu:configure', 'configure:ozu']; + protected $description = 'Sends CMS configuration to Ozu.'; private Client $ozuClient; + private array $processedCollections = []; + public function handle(Client $ozuClient): int { if (empty(config('ozu-client.collections'))) { @@ -28,9 +31,14 @@ public function handle(Client $ozuClient): int $this->ozuClient = $ozuClient; + $this->newLine(); + $this->line('Sending configuration to '.config('ozu-client.api_host').''); + $this->newLine(); + collect(config('ozu-client.collections')) ->each(fn ($collection, $k) => $this->updateCmsConfigurationFor($collection, $k+1)); + $this->newLine(); $this->info('CMS configuration sent to Ozu.'); return self::SUCCESS; @@ -44,6 +52,22 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo default => $collectionClass, }; + // avoid processing the same collection twice + if (in_array($model::class, $this->processedCollections)) { + /* + * We avoid processing multiple times the same subcollection, but we'll warn the + * user only if it's not a subcollection, because subcollection models can be + * declared in multiple parent collections. + */ + + if(!$isSubCollection) { + $this->line('Skipping '.($model->ozuCollectionKey() ?? $model::class).' because it has already been processed.'); + $this->line('You may have wrongly configured your subcollections, or included a subcollection to the collections array in the ozu-client config file...'); + } + + return; + } else { $this->processedCollections[] = $model::class; } + $collection = $model::configureOzuCollection(new OzuCollectionConfig()); $list = $model::configureOzuCollectionList(new OzuCollectionListConfig()); $form = $model::configureOzuCollectionForm(new OzuCollectionFormConfig()); @@ -52,7 +76,7 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo 'key' => $model->ozuCollectionKey(), 'label' => $collection->label(), 'icon' => $collection->icon(), - 'isMenu' => !$isSubCollection, + 'isSubCollection' => !$isSubCollection, 'hasPublicationState' => $collection->hasPublicationState(), 'autoDeployDateField' => $collection->autoDeployDateField(), 'isCreatable' => $collection->isCreatable(), @@ -97,7 +121,7 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo ]), ]; - $this->info('Update CMS configuration for ['.$payload['key'].'].'); + $this->line('Updating CMS configuration for '.$payload['key'].'...'); try { $this->ozuClient->updateCollectionSharpConfiguration($payload['key'], $payload); @@ -112,7 +136,17 @@ private function updateCmsConfigurationFor($collectionClass, int $order = 1, boo if (!isset($message['message'])) { throw $e; } - $this->error('['.$payload['key'].'] '.$message['message']); + + // Display by priority: validations errors, generic error, json dump of the response + $this->error(sprintf( + '[%s] %s', + $collection['key'], + isset($message['errors']) ? + collect(is_array($message['errors']) ? $message['errors'] : []) + ->map(fn ($error, $key) => sprintf('%s: %s', $key, $error[0])) + ->implode(', ') ?? ($message['message'] ?? json_encode($message)) + : ($message['message'] ?? json_encode($message)) + )); } else { throw $e; } diff --git a/tests/Console/ConfigureCmsCommandTest.php b/tests/Console/ConfigureCmsCommandTest.php index 4fcf649..b726b54 100644 --- a/tests/Console/ConfigureCmsCommandTest.php +++ b/tests/Console/ConfigureCmsCommandTest.php @@ -5,6 +5,7 @@ use Code16\OzuClient\OzuCms\OzuCollectionConfig; use Code16\OzuClient\OzuCms\OzuCollectionFormConfig; use Code16\OzuClient\OzuCms\OzuCollectionListConfig; +use Code16\OzuClient\Tests\Fixtures\DummySubcollectionTestModel; use Code16\OzuClient\Tests\Fixtures\DummyTestModel; use Illuminate\Console\Command; use Illuminate\Http\Client\Request; @@ -261,3 +262,52 @@ public function ozuCollectionKey(): string ]); }); }); + +it('sends subcollections configuration to Ozu', function () { + Http::fake(); + + // Create a parent collection with subcollections + $parentCollectionClass = new class extends DummyTestModel + { + public function ozuCollectionKey(): string + { + return 'dummy-parent'; + } + + public static function configureOzuCollection(OzuCollectionConfig $config): OzuCollectionConfig + { + return $config + ->setLabel('Parent collection') + ->addSubCollection(DummySubcollectionTestModel::class); + } + }; + + config(['ozu-client.collections' => [$parentCollectionClass]]); + + $this->artisan('ozu:configure-cms') + ->expectsOutput('CMS configuration sent to Ozu.') + ->assertExitCode(Command::SUCCESS); + + // Assert parent is sent with subcollections in payload + Http::assertSent(function (Request $request) { + return $request->url() == sprintf( + '%s/api/%s/%s/collections/%s/configure', + rtrim(config('ozu-client.api_host'), '/'), + config('ozu-client.api_version'), + 'test', + 'dummy-parent' + ) && $request['subCollections']->count() === 1 + && $request['subCollections']->first() === 'dummy-subcollection'; + }); + + // Assert subcollection is also processed and sent + Http::assertSent(function (Request $request) { + return $request->url() == sprintf( + '%s/api/%s/%s/collections/%s/configure', + rtrim(config('ozu-client.api_host'), '/'), + config('ozu-client.api_version'), + 'test', + 'dummy-subcollection' + ); + }); +}); diff --git a/tests/Fixtures/DummySubcollectionTestModel.php b/tests/Fixtures/DummySubcollectionTestModel.php new file mode 100644 index 0000000..f9ad079 --- /dev/null +++ b/tests/Fixtures/DummySubcollectionTestModel.php @@ -0,0 +1,38 @@ +