From 09355d14a45e3e5c410ea090e90c3396a37d9341 Mon Sep 17 00:00:00 2001 From: Sebastian Krupinski Date: Mon, 7 Sep 2026 13:39:16 -0400 Subject: [PATCH] feat: add services configuration accessors Signed-off-by: Sebastian Krupinski --- .../lib/Models/Tenant/TenantConfiguration.php | 5 ++ core/lib/Service/TenantService.php | 11 +++ core/lib/Stores/TenantStore.php | 85 ++++++++++++++++--- 3 files changed, 89 insertions(+), 12 deletions(-) diff --git a/core/lib/Models/Tenant/TenantConfiguration.php b/core/lib/Models/Tenant/TenantConfiguration.php index a85ed28..d50fa54 100644 --- a/core/lib/Models/Tenant/TenantConfiguration.php +++ b/core/lib/Models/Tenant/TenantConfiguration.php @@ -14,6 +14,7 @@ class TenantConfiguration extends JsonSerializableObject protected TenantFirewall $firewall; protected TenantStores $stores; protected TenantPreview $preview; + protected array $services = []; public function __construct() { @@ -36,6 +37,10 @@ class TenantConfiguration extends JsonSerializableObject return $this->firewall; } + public function services(): array { + return $this->services; + } + public function stores(): TenantStores { return $this->stores; } diff --git a/core/lib/Service/TenantService.php b/core/lib/Service/TenantService.php index b86ceee..f4dae04 100644 --- a/core/lib/Service/TenantService.php +++ b/core/lib/Service/TenantService.php @@ -73,4 +73,15 @@ class TenantService { return $this->store->storeSettings($identifier, $settings); } + + public function fetchServiceConfiguration(string $identifier, string $name): ?array + { + return $this->store->fetchServiceConfiguration($identifier, $name); + } + + public function storeServiceConfiguration(string $identifier, string $name, array $configuration): bool + { + return $this->store->storeServiceConfiguration($identifier, $name, $configuration); + } + } diff --git a/core/lib/Stores/TenantStore.php b/core/lib/Stores/TenantStore.php index bc4a446..a479b5e 100644 --- a/core/lib/Stores/TenantStore.php +++ b/core/lib/Stores/TenantStore.php @@ -77,29 +77,40 @@ class TenantStore $this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteOne(['_id' => new ObjectId($id)]); } - /** - * Atomically creates or replaces one logical store reference. - * - * @param array{provider: string, service: string|int, namespace: string} $reference - */ - public function storeConfigurationStore(string $identifier, string $name, array $reference): bool + // ========================================================================= + // Configuration Operations + // ========================================================================= + + public function fetchConfiguration(string $identifier, string $path): ?array + { + $entry = $this->dataStore->selectCollection(self::COLLECTION_NAME)->findOne( + ['identifier' => $identifier], + ['projection' => ['configuration.' . $path => 1]], + ); + + if (!$entry) { + return null; + } + + $value = $this->readPath($entry, "configuration.{$path}"); + return $value === null ? null : (array) $value; + } + + public function storeConfiguration(string $identifier, string $path, array $value): bool { $result = $this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne( ['identifier' => $identifier], - ['$set' => ["configuration.stores.{$name}" => $reference]], + ['$set' => ['configuration.' . $path => $value]], ); return $result->getMatchedCount() > 0; } - /** - * Atomically removes one logical store reference. - */ - public function removeConfigurationStore(string $identifier, string $name): ?bool + public function removeConfiguration(string $identifier, string $path): ?bool { $result = $this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne( ['identifier' => $identifier], - ['$unset' => ["configuration.stores.{$name}" => '']], + ['$unset' => ['configuration.' . $path => '']], ); if ($result->getMatchedCount() === 0) { @@ -162,4 +173,54 @@ class TenantStore return $result->getMatchedCount() > 0; } + /** + * Atomically creates or replaces one logical store reference. + * + * @param array{provider: string, service: string|int, namespace: string} $reference + */ + public function storeConfigurationStore(string $identifier, string $name, array $reference): bool + { + return $this->storeConfiguration($identifier, "stores.{$name}", $reference); + } + + /** + * Atomically removes one logical store reference. + */ + public function removeConfigurationStore(string $identifier, string $name): ?bool + { + return $this->removeConfiguration($identifier, "stores.{$name}"); + } + + public function fetchServiceConfiguration(string $identifier, string $name): ?array + { + $this->validateServiceName($name); + return $this->fetchConfiguration($identifier, "services.{$name}"); + } + + /** Save one service atomically. */ + public function storeServiceConfiguration(string $identifier, string $name, array $configuration): bool + { + $this->validateServiceName($name); + return $this->storeConfiguration($identifier, "services.{$name}", $configuration); + } + + private function validateServiceName(string $name): void + { + if (preg_match('/^[a-z][a-z0-9_]*$/D', $name) !== 1) { + throw new \InvalidArgumentException('Invalid service name.'); + } + } + + private function readPath(array $data, string $path): mixed + { + $value = $data; + foreach (explode('.', $path) as $segment) { + if (!is_array($value) || !array_key_exists($segment, $value)) { + return null; + } + $value = $value[$segment]; + } + return $value; + } + } \ No newline at end of file