fix: clean up manager logic
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -316,8 +316,8 @@ class DefaultController extends ControllerAbstract {
|
||||
$userId,
|
||||
$data['provider'],
|
||||
$data['identifier'],
|
||||
$data['data'],
|
||||
$data['delta'] ?? false,
|
||||
$data['data']
|
||||
);
|
||||
}
|
||||
|
||||
@@ -515,7 +515,7 @@ class DefaultController extends ControllerAbstract {
|
||||
$userId,
|
||||
$data['provider'],
|
||||
$data['service'],
|
||||
$targetIdentifier->collection() ?? null,
|
||||
$targetIdentifier ?? null,
|
||||
$data['properties']
|
||||
);
|
||||
}
|
||||
@@ -807,7 +807,7 @@ class DefaultController extends ControllerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
return $this->mailManager->entityDelete($tenantId, $userId, $sources);
|
||||
return $this->mailManager->entityDelete($tenantId, $userId, ...$sources->all());
|
||||
}
|
||||
|
||||
private function entityPatch(string $tenantId, string $userId, array $data): mixed {
|
||||
@@ -860,7 +860,7 @@ class DefaultController extends ControllerAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
return $this->mailManager->entityMove($tenantId, $userId, $target, $sources);
|
||||
return $this->mailManager->entityMove($tenantId, $userId, $target, ...$sources->all());
|
||||
}
|
||||
|
||||
private function entityTransmit(string $tenantId, string $userId, array $data): mixed {
|
||||
|
||||
361
lib/Manager.php
361
lib/Manager.php
@@ -7,19 +7,14 @@ namespace KTXM\MailManager;
|
||||
use InvalidArgumentException;
|
||||
use KTXC\Resource\ProviderManager;
|
||||
use KTXF\Mail\Collection\CollectionBaseInterface;
|
||||
use KTXF\Mail\Collection\CollectionMutableInterface;
|
||||
use KTXF\Mail\Collection\CollectionPropertiesMutableInterface;
|
||||
use KTXF\Mail\Collection\ICollectionBase;
|
||||
use KTXF\Mail\Entity\Address;
|
||||
use KTXF\Mail\Entity\IMessageBase;
|
||||
use KTXF\Mail\Entity\IMessageMutable;
|
||||
use KTXF\Mail\Exception\SendException;
|
||||
use KTXF\Mail\Object\MessagePropertiesMutableInterface;
|
||||
use KTXF\Mail\Provider\ProviderBaseInterface;
|
||||
use KTXF\Mail\Provider\ProviderServiceDiscoverInterface;
|
||||
use KTXF\Mail\Provider\ProviderServiceMutateInterface;
|
||||
use KTXF\Mail\Provider\ProviderServiceTestInterface;
|
||||
use KTXF\Mail\Queue\SendOptions;
|
||||
use KTXF\Mail\Service\IServiceSend;
|
||||
use KTXF\Mail\Service\ServiceBaseInterface;
|
||||
use KTXF\Mail\Service\ServiceCollectionMutableInterface;
|
||||
use KTXF\Mail\Service\ServiceConfigurableInterface;
|
||||
@@ -28,11 +23,9 @@ use KTXF\Mail\Service\ServiceMutableInterface;
|
||||
use KTXF\Resource\Filter\IFilter;
|
||||
use KTXF\Resource\Identifier\CollectionIdentifier;
|
||||
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||
use KTXF\Resource\Identifier\ResourceIdentifier;
|
||||
use KTXF\Resource\Identifier\ResourceIdentifiers;
|
||||
use KTXF\Resource\Provider\ResourceServiceIdentityInterface;
|
||||
use KTXF\Resource\Provider\ResourceServiceLocationInterface;
|
||||
use KTXF\Resource\Range\IRange;
|
||||
use KTXF\Resource\Range\RangeAnchorType;
|
||||
use KTXF\Resource\Range\RangeType;
|
||||
use KTXF\Resource\Selector\CollectionSelector;
|
||||
@@ -40,7 +33,6 @@ use KTXF\Resource\Selector\EntitySelector;
|
||||
use KTXF\Resource\Selector\ServiceSelector;
|
||||
use KTXF\Resource\Selector\SourceSelector;
|
||||
use KTXF\Resource\Sort\ISort;
|
||||
use KTXM\MailManager\Queue\MailQueueFile;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
@@ -53,7 +45,6 @@ class Manager {
|
||||
public function __construct(
|
||||
private LoggerInterface $logger,
|
||||
private ProviderManager $providerManager,
|
||||
private MailQueueFile $queue,
|
||||
) { }
|
||||
|
||||
/**
|
||||
@@ -227,6 +218,9 @@ class Manager {
|
||||
if ($provider instanceof ProviderServiceMutateInterface === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' does not support service creation");
|
||||
}
|
||||
if ($provider->capable(ProviderServiceMutateInterface::CAPABILITY_SERVICE_CREATE) === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' is not capable of creating services");
|
||||
}
|
||||
|
||||
// Create a fresh service instance
|
||||
$service = $provider->serviceFresh();
|
||||
@@ -250,19 +244,22 @@ class Manager {
|
||||
* @param string $userId User identifier for context
|
||||
* @param string $providerId Provider identifier
|
||||
* @param string|int $serviceId Service identifier
|
||||
* @param bool $delta Whether the update is a delta (partial) update or a full replacement
|
||||
* @param array $data Updated service configuration data
|
||||
* @param bool $delta Whether the update is a delta (partial) update or a full replacement
|
||||
*
|
||||
* @return ServiceBaseInterface Updated service
|
||||
*
|
||||
* @throws InvalidArgumentException If provider doesn't support service modification or service not found
|
||||
*/
|
||||
public function serviceUpdate(string $tenantId, string $userId, string $providerId, string|int $serviceId, bool $delta = false, array $data): ServiceBaseInterface {
|
||||
public function serviceUpdate(string $tenantId, string $userId, string $providerId, string|int $serviceId, array $data, bool $delta = false): ServiceBaseInterface {
|
||||
// retrieve provider and service
|
||||
$provider = $this->providerFetch($tenantId, $userId, $providerId);
|
||||
if ($provider instanceof ProviderServiceMutateInterface === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' does not support service modification");
|
||||
}
|
||||
if ($provider->capable(ProviderServiceMutateInterface::CAPABILITY_SERVICE_MODIFY) === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' is not capable of modifying services");
|
||||
}
|
||||
|
||||
// Fetch existing service
|
||||
$service = $provider->serviceFetch($tenantId, $userId, $serviceId);
|
||||
@@ -303,6 +300,9 @@ class Manager {
|
||||
if ($provider instanceof ProviderServiceMutateInterface === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' does not support service deletion");
|
||||
}
|
||||
if ($provider->capable(ProviderServiceMutateInterface::CAPABILITY_SERVICE_DESTROY) === false) {
|
||||
throw new InvalidArgumentException("Provider '$providerId' is not capable of deleting services");
|
||||
}
|
||||
|
||||
// Fetch existing service
|
||||
$service = $provider->serviceFetch($tenantId, $userId, $serviceId);
|
||||
@@ -339,7 +339,7 @@ class Manager {
|
||||
$providers = $this->providerList($tenantId, $userId, $providerId !== null ? new SourceSelector([$providerId => true]) : null);
|
||||
|
||||
foreach ($providers as $currentProviderId => $provider) {
|
||||
if (!($provider instanceof ProviderServiceDiscoverInterface)) {
|
||||
if ($provider instanceof ProviderServiceDiscoverInterface === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -399,7 +399,10 @@ class Manager {
|
||||
if ($service === null) {
|
||||
throw new InvalidArgumentException("Service not found: $providerId/$serviceId");
|
||||
}
|
||||
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service $providerId/$serviceId is disabled");
|
||||
}
|
||||
// test service
|
||||
try {
|
||||
return $provider->serviceTest($service);
|
||||
} catch (\Throwable $e) {
|
||||
@@ -471,6 +474,9 @@ class Manager {
|
||||
$services = $provider->serviceList($tenantId, $userId, $serviceFilter);
|
||||
// retrieve collections for each service
|
||||
foreach ($services as $service) {
|
||||
if ($service->getEnabled() === false) {
|
||||
continue;
|
||||
}
|
||||
// construct filter for collections
|
||||
$collectionFilter = null;
|
||||
if ($filter !== null && $filter !== []) {
|
||||
@@ -518,26 +524,29 @@ class Manager {
|
||||
|
||||
// check services and collections for each available provider
|
||||
foreach ($providers as $provider) {
|
||||
// extract services for this provider
|
||||
$serviceSelector = $sources[$provider->identifier()];
|
||||
$servicesRequested = $serviceSelector->identifiers();
|
||||
/** @var ServiceBaseInterface[] $servicesAvailable */
|
||||
$servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested);
|
||||
$servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable));
|
||||
|
||||
// mark unavailable services as false
|
||||
if ($servicesUnavailable !== []) {
|
||||
$responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false);
|
||||
}
|
||||
|
||||
// confirm collections for each available service
|
||||
foreach ($servicesAvailable as $service) {
|
||||
// omit disabled services
|
||||
if ($service->getEnabled() === false) {
|
||||
$responseData[$provider->identifier()][$service->identifier()] = false;
|
||||
continue;
|
||||
}
|
||||
// extract collections requested for this service
|
||||
$collectionSelector = $serviceSelector[$service->identifier()];
|
||||
$collectionsRequested = $collectionSelector->identifiers();
|
||||
|
||||
$collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
|
||||
if ($collectionsRequested === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check each requested collection
|
||||
$collectionsAvailable = $service->collectionExtant(...$collectionsRequested);
|
||||
$collectionsUnavailable = array_diff($collectionsRequested, array_keys($collectionsAvailable));
|
||||
@@ -566,7 +575,7 @@ class Manager {
|
||||
public function collectionFetch(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId): ?CollectionBaseInterface {
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
|
||||
if ($service === null) {
|
||||
if ($service === null || $service->getEnabled() === false) {
|
||||
return null;
|
||||
}
|
||||
// retrieve collection
|
||||
@@ -588,21 +597,22 @@ class Manager {
|
||||
public function collectionCreate(string $tenantId, string $userId, string $provider, string|int $service, CollectionIdentifier|null $target, CollectionPropertiesMutableInterface|array $properties, array $options = []): CollectionBaseInterface {
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $provider, $service);
|
||||
|
||||
// Check if service supports collection creation
|
||||
if (!($service instanceof ServiceCollectionMutableInterface)) {
|
||||
throw new InvalidArgumentException("Service does not support collection mutations");
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled");
|
||||
}
|
||||
if ($service instanceof ServiceCollectionMutableInterface === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support collection mutations");
|
||||
}
|
||||
if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_CREATE)) {
|
||||
throw new InvalidArgumentException("Service is not capable of creating collections");
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' is not capable of creating collections");
|
||||
}
|
||||
|
||||
// convert properties if necessary
|
||||
if (is_array($properties)) {
|
||||
$collection = $service->collectionFresh()->getProperties()->jsonDeserialize($properties);
|
||||
} else {
|
||||
$collection = $properties;
|
||||
}
|
||||
|
||||
// Create collection
|
||||
return $service->collectionCreate($target, $collection, $options);
|
||||
}
|
||||
@@ -621,21 +631,22 @@ class Manager {
|
||||
public function collectionUpdate(string $tenantId, string $userId, CollectionIdentifier $target, CollectionPropertiesMutableInterface|array $properties): CollectionBaseInterface {
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service());
|
||||
|
||||
// Check if service supports collection creation
|
||||
if (!($service instanceof ServiceCollectionMutableInterface)) {
|
||||
throw new InvalidArgumentException("Service does not support collection mutations");
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled");
|
||||
}
|
||||
if ($service instanceof ServiceCollectionMutableInterface === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support collection mutations");
|
||||
}
|
||||
if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_UPDATE)) {
|
||||
throw new InvalidArgumentException("Service is not capable of updating collections");
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' is not capable of updating collections");
|
||||
}
|
||||
|
||||
// convert properties if necessary
|
||||
if (is_array($properties)) {
|
||||
$mutation = $service->collectionFresh()->getProperties()->jsonDeserialize($properties);
|
||||
} else {
|
||||
$mutation = $properties;
|
||||
}
|
||||
|
||||
// Update collection
|
||||
return $service->collectionUpdate($target, $mutation);
|
||||
}
|
||||
@@ -655,17 +666,18 @@ class Manager {
|
||||
public function collectionDelete(string $tenantId, ?string $userId, CollectionIdentifier $target, array $options = []): CollectionBaseInterface | bool {
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service());
|
||||
|
||||
// Check if service supports collection deletion
|
||||
if (!($service instanceof ServiceCollectionMutableInterface)) {
|
||||
throw new InvalidArgumentException("Service does not support collection mutations");
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled");
|
||||
}
|
||||
if ($service instanceof ServiceCollectionMutableInterface === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support collection mutations");
|
||||
}
|
||||
if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_DELETE)) {
|
||||
throw new InvalidArgumentException("Service is not capable of deleting collections");
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' is not capable of deleting collections");
|
||||
}
|
||||
|
||||
// convert options
|
||||
$force = $options['force'] ?? false;
|
||||
|
||||
// delete collection
|
||||
return $service->collectionDelete($target, $force);
|
||||
}
|
||||
@@ -685,23 +697,24 @@ class Manager {
|
||||
public function collectionMove(string $tenantId, ?string $userId, CollectionIdentifier $target, CollectionIdentifier $source): CollectionBaseInterface {
|
||||
// validate that source and target are the same provider and service
|
||||
if ($source->provider() !== $target->provider() || $source->service() !== $target->service()) {
|
||||
throw new InvalidArgumentException("Source and target collections must belong to the same provider and service");
|
||||
throw new InvalidArgumentException("Source '{$source->service()}' and target '{$target->service()}' collections must belong to the same provider and service");
|
||||
}
|
||||
// Validate that source and target are not the same
|
||||
if ($source->collection() === $target->collection()) {
|
||||
throw new InvalidArgumentException("Source and target collections are the same");
|
||||
throw new InvalidArgumentException("Source '{$source->collection()}' and target '{$target->collection()}' collections are the same");
|
||||
}
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $source->provider(), $source->service());
|
||||
|
||||
// Check if service supports collection move
|
||||
if (!($service instanceof ServiceCollectionMutableInterface)) {
|
||||
throw new InvalidArgumentException("Service does not support collection mutations");
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' not found or is disabled");
|
||||
}
|
||||
if ($service instanceof ServiceCollectionMutableInterface === false) {
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' does not support collection mutations");
|
||||
}
|
||||
if (!$service->capable(ServiceCollectionMutableInterface::CAPABILITY_COLLECTION_MOVE)) {
|
||||
throw new InvalidArgumentException("Service is not capable of moving collections");
|
||||
throw new InvalidArgumentException("Service '{$service->identifier()}' is not capable of moving collections");
|
||||
}
|
||||
|
||||
// move collection
|
||||
return $service->collectionMove($target, $source);
|
||||
}
|
||||
@@ -733,6 +746,10 @@ class Manager {
|
||||
$servicesSelected = $provider->serviceList($tenantId,$userId, $serviceSelector->identifiers());
|
||||
/** @var ServiceBaseInterface $service */
|
||||
foreach ($servicesSelected as $service) {
|
||||
// omit disabled services
|
||||
if ($service->getEnabled() === false) {
|
||||
continue;
|
||||
}
|
||||
// retrieve collections for each service
|
||||
$collectionSelector = $serviceSelector[$service->identifier()];
|
||||
$collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
|
||||
@@ -818,6 +835,10 @@ class Manager {
|
||||
$servicesSelected = $provider->serviceList($tenantId, $userId, $serviceSelector->identifiers());
|
||||
/** @var ServiceBaseInterface $service */
|
||||
foreach ($servicesSelected as $service) {
|
||||
// omit disabled services
|
||||
if ($service->getEnabled() === false) {
|
||||
continue;
|
||||
}
|
||||
// retrieve collections for each service
|
||||
$collectionSelector = $serviceSelector[$service->identifier()];
|
||||
$collectionSelected = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
|
||||
@@ -888,7 +909,9 @@ class Manager {
|
||||
*/
|
||||
public function entityFetch(string $tenantId, ?string $userId, string $providerId, string|int $serviceId, string|int $collectionId, array $identifiers): array {
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
|
||||
|
||||
if ($service->getEnabled() === false) {
|
||||
throw new InvalidArgumentException("Service '{$providerId}:{$serviceId}' not found or is disabled");
|
||||
}
|
||||
// retrieve collection
|
||||
return $service->entityFetch($collectionId, ...$identifiers);
|
||||
}
|
||||
@@ -913,46 +936,44 @@ class Manager {
|
||||
$providers = $this->providerList($tenantId, $userId, $sources);
|
||||
$providersRequested = $sources->identifiers();
|
||||
$providersUnavailable = array_diff($providersRequested, array_keys($providers));
|
||||
|
||||
// initialize response with unavailable providers
|
||||
$responseData = array_fill_keys($providersUnavailable, false);
|
||||
|
||||
// check services, collections, and entities for each available provider
|
||||
foreach ($providers as $provider) {
|
||||
// extract services requested for this provider
|
||||
$serviceSelector = $sources[$provider->identifier()];
|
||||
$servicesRequested = $serviceSelector->identifiers();
|
||||
/** @var ServiceBaseInterface[] $servicesAvailable */
|
||||
$servicesAvailable = $provider->serviceList($tenantId, $userId, $servicesRequested);
|
||||
$servicesUnavailable = array_diff($servicesRequested, array_keys($servicesAvailable));
|
||||
|
||||
// mark unavailable services as false
|
||||
if ($servicesUnavailable !== []) {
|
||||
$responseData[$provider->identifier()] = array_fill_keys($servicesUnavailable, false);
|
||||
}
|
||||
|
||||
// check collections and entities for each available service
|
||||
foreach ($servicesAvailable as $service) {
|
||||
// omit disabled services
|
||||
if ($service->getEnabled() === false) {
|
||||
$responseData[$provider->identifier()][$service->identifier()] = false;
|
||||
continue;
|
||||
}
|
||||
// extract collections requested for this service
|
||||
$collectionSelector = $serviceSelector[$service->identifier()];
|
||||
$collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
|
||||
|
||||
if ($collectionsRequested === []) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check entities for each requested collection
|
||||
foreach ($collectionsRequested as $collectionId) {
|
||||
// first check if collection exists
|
||||
$collectionExists = $service->collectionExtant((string)$collectionId);
|
||||
|
||||
if (!$collectionExists) {
|
||||
// collection doesn't exist, mark as false
|
||||
$responseData[$provider->identifier()][$service->identifier()][$collectionId] = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
// extract entity identifiers from collection selector
|
||||
$entitySelector = $collectionSelector[$collectionId];
|
||||
|
||||
// handle both array of entity IDs and boolean true (meaning check if collection exists)
|
||||
if ($entitySelector instanceof EntitySelector) {
|
||||
// check specific entities within the collection
|
||||
@@ -991,6 +1012,7 @@ class Manager {
|
||||
$responseData = array_fill_keys($providersUnavailable, false);
|
||||
// iterate through available providers
|
||||
foreach ($providers as $provider) {
|
||||
// extract services requested for this provider
|
||||
$serviceSelector = $sources[$provider->identifier()];
|
||||
$servicesRequested = $serviceSelector instanceof ServiceSelector ? $serviceSelector->identifiers() : [];
|
||||
/** @var ServiceBaseInterface[] $services */
|
||||
@@ -1001,12 +1023,19 @@ class Manager {
|
||||
}
|
||||
// iterate through available services
|
||||
foreach ($services as $service) {
|
||||
// omit disabled services
|
||||
if ($service->getEnabled() === false) {
|
||||
$responseData[$provider->identifier()][$service->identifier()] = false;
|
||||
continue;
|
||||
}
|
||||
// extract collections requested for this service
|
||||
$collectionSelector = $serviceSelector[$service->identifier()];
|
||||
$collectionsRequested = $collectionSelector instanceof CollectionSelector ? $collectionSelector->identifiers() : [];
|
||||
if ($collectionsRequested === []) {
|
||||
$responseData[$provider->identifier()][$service->identifier()] = false;
|
||||
continue;
|
||||
}
|
||||
// check delta for each requested collection
|
||||
foreach ($collectionsRequested as $collection) {
|
||||
$entitySelector = $collectionSelector[$collection] ?? null;
|
||||
$responseData[$provider->identifier()][$service->identifier()][$collection] = $service->entityDelta($collection, $entitySelector);
|
||||
@@ -1017,61 +1046,72 @@ class Manager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes entities
|
||||
* Patches entities
|
||||
*
|
||||
* @since 2026.04.01
|
||||
*
|
||||
* @param string $tenantId Tenant identifier
|
||||
* @param string|null $userId User identifier for context
|
||||
* @param ResourceIdentifiers $sources Source entities to delete
|
||||
* @param MessagePropertiesMutableInterface|array $properties Properties to patch
|
||||
* @param EntityIdentifier ...$targets Target entities to patch
|
||||
*
|
||||
* @return array<string, array{
|
||||
* disposition: 'moved'|'deleted'|'error',
|
||||
* destination: ?CollectionIdentifier,
|
||||
* mutation: EntityIdentifier
|
||||
* disposition: 'patched'|'error',
|
||||
* error?: string
|
||||
* }> Results keyed by source entity identifier
|
||||
*/
|
||||
public function entityDelete(string $tenantId, string $userId, ResourceIdentifiers $sources): array {
|
||||
public function entityPatch(string $tenantId, string $userId, MessagePropertiesMutableInterface|array $properties, EntityIdentifier ...$targets): array {
|
||||
$operationOutcome = [];
|
||||
$targetIdentifiers = new ResourceIdentifiers();
|
||||
|
||||
foreach ($sources->providers() as $providerName) {
|
||||
$providerSources = $sources->byProvider($providerName);
|
||||
foreach ($providerSources->services() as $serviceName) {
|
||||
$serviceSources = $providerSources->byService($serviceName);
|
||||
foreach ($targets as $target) {
|
||||
$targetIdentifiers->add($target);
|
||||
}
|
||||
|
||||
// process targets grouped by provider
|
||||
foreach ($targetIdentifiers->providers() as $providerId) {
|
||||
// retrieve provider and validate
|
||||
$provider = $this->providerFetch($tenantId, $userId, $providerId);
|
||||
$providerTargets = $targetIdentifiers->byProvider($providerId);
|
||||
// provider not found, mark all identifiers as failed and continue to next provider
|
||||
if ($provider === null) {
|
||||
foreach ($providerTargets->services() as $serviceId) {
|
||||
$serviceTargets = $providerTargets->byService($serviceId);
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[$identifier] = ['disposition' => 'error', 'error' => 'provider not found'];
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// process targets grouped by service for this provider
|
||||
foreach ($providerTargets->services() as $serviceId) {
|
||||
// extract services requested for this provider
|
||||
$serviceTargets = $providerTargets->byService($serviceId);
|
||||
// retrieve and validate service
|
||||
$service = null;
|
||||
try {
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerName, $serviceName);
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
|
||||
} catch (\Throwable $e) {
|
||||
// Service not found, mark all identifiers as failed
|
||||
$error = "Service $serviceId not found";
|
||||
}
|
||||
|
||||
if ($service === null) {
|
||||
throw new InvalidArgumentException("Service not found: $providerName/$serviceName");
|
||||
if ($service instanceof ServiceEntityMutableInterface === false && !isset($error)) {
|
||||
$error = "Service $serviceId does not support entity mutation";
|
||||
}
|
||||
|
||||
// Temporarily disabled check until all methods are properly implemented from ServiceEntityMutableInterface
|
||||
if (!($service instanceof ServiceEntityMutableInterface)) {
|
||||
foreach ($serviceSources as $identifier) {
|
||||
$operationOutcome[(string)$identifier] = [
|
||||
'success' => false,
|
||||
'error' => 'serviceNotEntityMutable',
|
||||
];
|
||||
if (!isset($error) && !$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_PATCH)) {
|
||||
$error = "Service $serviceId does not support entity patching";
|
||||
}
|
||||
// on error, mark all identifiers for this service as failed and continue to next service
|
||||
if (isset($error)) {
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[(string)$identifier] = ['disposition' => 'error','error' => $error];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_DELETE)) {
|
||||
foreach ($serviceSources as $identifier) {
|
||||
$operationOutcome[(string)$identifier] = [
|
||||
'success' => false,
|
||||
'error' => 'serviceCannotDeleteEntities',
|
||||
];
|
||||
/** @var ServiceEntityMutableInterface $service */
|
||||
if ($properties instanceof MessagePropertiesMutableInterface === false) {
|
||||
$properties = $service->entityFresh()->getProperties()->jsonDeserialize($properties);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
return $service->entityDelete(...$serviceSources->all());
|
||||
$operationOutcome = array_merge($operationOutcome, $service->entityPatch($properties, ...$serviceTargets->all()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1079,67 +1119,69 @@ class Manager {
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches entities
|
||||
* Deletes entities
|
||||
*
|
||||
* @since 2026.04.01
|
||||
*
|
||||
* @param string $tenantId Tenant identifier
|
||||
* @param string|null $userId User identifier for context
|
||||
* @param ResourceIdentifiers $sources Source entities to patch
|
||||
* @param array $patchData Data to patch
|
||||
* @param EntityIdentifier $targets Source entities to delete
|
||||
*
|
||||
* @return array<string, array{
|
||||
* disposition: 'patched'|'error',
|
||||
* error?: string
|
||||
* disposition: 'moved'|'deleted'|'error',
|
||||
* destination: ?CollectionIdentifier,
|
||||
* mutation: EntityIdentifier
|
||||
* }> Results keyed by source entity identifier
|
||||
*/
|
||||
public function entityPatch(string $tenantId, string $userId, ResourceIdentifiers $targets, array $patch): array {
|
||||
public function entityDelete(string $tenantId, string $userId, EntityIdentifier ...$targets): array {
|
||||
$operationOutcome = [];
|
||||
$targetIdentifiers = new ResourceIdentifiers();
|
||||
|
||||
// Process targets grouped by provider and service to minimize redundant fetches and operations
|
||||
foreach ($targets->providers() as $providerName) {
|
||||
$providerTargets = $targets->byProvider($providerName);
|
||||
$provider = $this->providerFetch($tenantId, $userId, $providerName);
|
||||
foreach ($targets as $target) {
|
||||
$targetIdentifiers->add($target);
|
||||
}
|
||||
|
||||
// If provider is not found, mark all identifiers as failed and continue to next provider
|
||||
// process targets grouped by provider
|
||||
foreach ($targetIdentifiers->providers() as $providerId) {
|
||||
// retrieve provider and validate
|
||||
$provider = $this->providerFetch($tenantId, $userId, $providerId);
|
||||
$providerTargets = $targetIdentifiers->byProvider($providerId);
|
||||
// provider not found, mark all identifiers as failed and continue to next provider
|
||||
if ($provider === null) {
|
||||
foreach ($providerTargets->services() as $serviceName) {
|
||||
$serviceTargets = $providerTargets->byService($serviceName);
|
||||
foreach ($providerTargets->services() as $serviceId) {
|
||||
$serviceTargets = $providerTargets->byService($serviceId);
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[$identifier] = ['disposition' => 'error', 'error' => 'provider not found'];
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($providerTargets->services() as $serviceName) {
|
||||
$serviceTargets = $providerTargets->byService($serviceName);
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerName, $serviceName);
|
||||
// If service is not found, mark all identifiers as failed and continue to next service
|
||||
if ($service === null) {
|
||||
// process targets grouped by service for this provider
|
||||
foreach ($providerTargets->services() as $serviceId) {
|
||||
// extract services requested for this provider
|
||||
$serviceTargets = $providerTargets->byService($serviceId);
|
||||
// retrieve and validate service
|
||||
$service = null;
|
||||
try {
|
||||
$service = $this->serviceFetch($tenantId, $userId, $providerId, $serviceId);
|
||||
} catch (\Throwable $e) {
|
||||
$error = "Service $serviceId not found";
|
||||
}
|
||||
if ($service instanceof ServiceEntityMutableInterface === false && !isset($error)) {
|
||||
$error = "Service $serviceId does not support entity mutation";
|
||||
}
|
||||
if (!isset($error) && !$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_DELETE)) {
|
||||
$error = "Service $serviceId does not support entity deletion";
|
||||
}
|
||||
// on error, mark all identifiers for this service as failed and continue to next service
|
||||
if (isset($error)) {
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[$identifier] = ['disposition' => 'error', 'error' => 'service not found'];
|
||||
$operationOutcome[(string)$identifier] = ['disposition' => 'error', 'error' => $error];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// If service does not support entity mutation, mark all identifiers as failed and continue to next service
|
||||
if (!($service instanceof ServiceEntityMutableInterface)) {
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[$identifier] = ['disposition' => 'error', 'error' => 'entity mutations not supported'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// If service does not support entity patching, mark all identifiers as failed and continue to next service
|
||||
if (!$service->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_PATCH)) {
|
||||
foreach ($serviceTargets as $identifier) {
|
||||
$operationOutcome[$identifier] = ['disposition' => 'error', 'error' => 'entity patching not supported'];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$properties = $service->entityFresh()->getProperties()->jsonDeserialize($patch);
|
||||
|
||||
return $service->entityPatch(...$serviceTargets->all(), patch: $patch);
|
||||
/** @var ServiceEntityMutableInterface $service */
|
||||
$operationOutcome = array_merge($operationOutcome, $service->entityDelete(...$serviceTargets->all()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1154,7 +1196,7 @@ class Manager {
|
||||
* @param string $tenantId Tenant identifier
|
||||
* @param string|null $userId User identifier for context
|
||||
* @param CollectionIdentifier $target Target collection identifier
|
||||
* @param ResourceIdentifiers $sources Source entities to move
|
||||
* @param EntityIdentifier ...$sources Source entities to move
|
||||
*
|
||||
* @return array<string, array{
|
||||
* disposition: 'moved'|'error',
|
||||
@@ -1162,24 +1204,54 @@ class Manager {
|
||||
* mutation: EntityIdentifier
|
||||
* }> Results keyed by source entity identifier
|
||||
*/
|
||||
public function entityMove(string $tenantId, string $userId, CollectionIdentifier $target, ResourceIdentifiers $sources): array {
|
||||
|
||||
$targetService = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service());
|
||||
|
||||
// Check if service supports entity move
|
||||
if ($targetService instanceof ServiceEntityMutableInterface === false) {
|
||||
return [];
|
||||
}
|
||||
if (!$targetService->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_MOVE)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
public function entityMove(string $tenantId, string $userId, CollectionIdentifier $target, EntityIdentifier ...$sources): array {
|
||||
$operationOutcome = [];
|
||||
|
||||
$destinationSources = $sources->byProvider($targetService->provider())->byService((string)$targetService->identifier());
|
||||
if (!$destinationSources->isEmpty()) {
|
||||
$operationOutcome = $targetService->entityMove($target, ...$destinationSources->all());
|
||||
// retrieve and validate service
|
||||
$targetService = null;
|
||||
try {
|
||||
$targetService = $this->serviceFetch($tenantId, $userId, $target->provider(), $target->service());
|
||||
} catch (\Throwable $e) {
|
||||
// do nothing here, error will be handled in validation below
|
||||
}
|
||||
if ($targetService === null || $targetService->getEnabled() === false) {
|
||||
$error = "Service {$target->service()} not found or is disabled";
|
||||
}
|
||||
/** @var ServiceEntityBaseInterface $targetService */
|
||||
if ($targetService instanceof ServiceEntityMutableInterface === false && !isset($error)) {
|
||||
$error = "Service {$targetService->identifier()} does not support entity mutation";
|
||||
}
|
||||
if (!isset($error) && !$targetService->capable(ServiceEntityMutableInterface::CAPABILITY_ENTITY_MOVE)) {
|
||||
$error = "Service {$targetService->identifier()} does not support entity moving";
|
||||
}
|
||||
// on error, mark all identifiers for this service as failed and continue to next service
|
||||
if (isset($error)) {
|
||||
foreach ($sources as $identifier) {
|
||||
$operationOutcome[(string)$identifier] = ['disposition' => 'error','error' => $error];
|
||||
}
|
||||
return $operationOutcome;
|
||||
}
|
||||
// validate that sources and target are the same service and group sources by service for processing
|
||||
$groupedSources = [];
|
||||
foreach ($sources as $source) {
|
||||
if ($source->provider() !== $target->provider() || $source->service() !== $target->service()) {
|
||||
$operationOutcome[(string)$source] = [
|
||||
'disposition' => 'error',
|
||||
'error' => "Source '{$source}' and target '{$target}' collections must belong to the same provider and service"
|
||||
];
|
||||
continue;
|
||||
}
|
||||
$groupedSources[$source->provider()][$source->service()][] = $source;
|
||||
}
|
||||
|
||||
if ($groupedSources === []) {
|
||||
return $operationOutcome;
|
||||
}
|
||||
|
||||
// perform operation for entities on the same service as the target
|
||||
$operationOutcome = array_merge(
|
||||
$operationOutcome,
|
||||
$targetService->entityMove($target, ...$groupedSources[$target->provider()][$target->service()])
|
||||
);
|
||||
|
||||
// TODO: Handle moving entities across different services/providers by fetching each entity and re-creating it in the target collection,
|
||||
// then deleting the original if the move is successful. This will require additional logic to handle potential failures and ensure data integrity.
|
||||
@@ -1187,4 +1259,5 @@ class Manager {
|
||||
return $operationOutcome;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -203,6 +203,7 @@ function handleNextStep() {
|
||||
|
||||
async function handleDiscover() {
|
||||
// Move to discovery status screen
|
||||
isManualMode.value = false
|
||||
currentStep.value = DISCOVERY_STEPS.DISCOVERY
|
||||
|
||||
// Extract provider IDs
|
||||
|
||||
Reference in New Issue
Block a user