* SPDX-License-Identifier: AGPL-3.0-or-later */ namespace KTXM\FileManager\Controllers; use InvalidArgumentException; use KTXC\Http\Response\JsonResponse; use KTXC\SessionIdentity; use KTXC\SessionTenant; use KTXF\Controller\ControllerAbstract; use KTXF\Resource\Selector\SourceSelector; use KTXF\Routing\Attributes\AuthenticatedRoute; use KTXM\FileManager\Manager; use Psr\Log\LoggerInterface; use Throwable; class DefaultController extends ControllerAbstract { public function __construct( private readonly SessionTenant $tenantIdentity, private readonly SessionIdentity $userIdentity, private Manager $fileManager, private readonly LoggerInterface $logger ) {} /** * Main API endpoint for file operations * * @return JsonResponse */ #[AuthenticatedRoute('/v1', name: 'filemanager.v1', methods: ['POST'])] public function index(int $version, string $transaction, string $operation, array $data = [], string|null $user = null): JsonResponse { // authorize request $tenantId = $this->tenantIdentity->identifier(); $userId = $this->userIdentity->identifier(); try { $data = $this->process($tenantId, $userId, $operation, $data); return new JsonResponse([ 'version' => $version, 'transaction' => $transaction, 'operation' => $operation, 'status' => 'success', 'data' => $data ], JsonResponse::HTTP_OK); } catch (Throwable $t) { $this->logger->error('Error processing file manager request', ['exception' => $t]); return new JsonResponse([ 'version' => $version, 'transaction' => $transaction, 'operation' => $operation, 'status' => 'error', 'error' => [ 'code' => $t->getCode(), 'message' => $t->getMessage() ] ], JsonResponse::HTTP_INTERNAL_SERVER_ERROR); } } private function process(string $tenantId, string $userId, string $operation, array $data): mixed { return match ($operation) { // Provider operations 'provider.list' => $this->providerList($tenantId, $userId, $data), 'provider.extant' => $this->providerExtant($tenantId, $userId, $data), // Service operations 'service.list' => $this->serviceList($tenantId, $userId, $data), 'service.extant' => $this->serviceExtant($tenantId, $userId, $data), 'service.fetch' => $this->serviceFetch($tenantId, $userId, $data), // Collection operations 'collection.list' => $this->collectionList($tenantId, $userId, $data), 'collection.extant' => $this->collectionExtant($tenantId, $userId, $data), 'collection.fetch' => $this->collectionFetch($tenantId, $userId, $data), 'collection.create' => $this->collectionCreate($tenantId, $userId, $data), 'collection.modify' => $this->collectionModify($tenantId, $userId, $data), 'collection.destroy' => $this->collectionDestroy($tenantId, $userId, $data), 'collection.copy' => $this->collectionCopy($tenantId, $userId, $data), 'collection.move' => $this->collectionMove($tenantId, $userId, $data), // Entity operations 'entity.list' => $this->entityList($tenantId, $userId, $data), 'entity.delta' => $this->entityDelta($tenantId, $userId, $data), 'entity.extant' => $this->entityExtant($tenantId, $userId, $data), 'entity.fetch' => $this->entityFetch($tenantId, $userId, $data), 'entity.read' => $this->entityRead($tenantId, $userId, $data), 'entity.create' => $this->entityCreate($tenantId, $userId, $data), 'entity.modify' => $this->entityModify($tenantId, $userId, $data), 'entity.destroy' => $this->entityDestroy($tenantId, $userId, $data), 'entity.copy' => $this->entityCopy($tenantId, $userId, $data), 'entity.move' => $this->entityMove($tenantId, $userId, $data), 'entity.write' => $this->entityWrite($tenantId, $userId, $data), // Node operations (unified recursive) 'node.list' => $this->nodeList($tenantId, $userId, $data), 'node.delta' => $this->nodeDelta($tenantId, $userId, $data), default => throw new InvalidArgumentException('Unknown operation: ' . $operation) }; } // ==================== Provider Operations ==================== private function providerList(string $tenantId, string $userId, array $data = []): mixed { $sources = null; if (isset($data['sources']) && is_array($data['sources'])) { $sources = new SourceSelector(); $sources->jsonDeserialize($data['sources']); } return $this->fileManager->providerList($tenantId, $userId, $sources); } private function providerExtant(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['sources']) || !is_array($data['sources'])) { throw new InvalidArgumentException('Invalid sources selector provided'); } $sources = new SourceSelector(); $sources->jsonDeserialize($data['sources']); // retrieve providers return $this->fileManager->providerExtant($tenantId, $userId, $sources); } // ==================== Service Operations ==================== private function serviceList(string $tenantId, string $userId, array $data = []): mixed { $sources = null; if (isset($data['sources']) && is_array($data['sources'])) { $sources = new SourceSelector(); $sources->jsonDeserialize($data['sources']); } // retrieve services return $this->fileManager->serviceList($tenantId, $userId, $sources); } private function serviceExtant(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['sources']) || !is_array($data['sources'])) { throw new InvalidArgumentException('Invalid sources selector provided'); } $sources = new SourceSelector(); $sources->jsonDeserialize($data['sources']); // retrieve services return $this->fileManager->serviceExtant($tenantId, $userId, $sources); } private function serviceFetch(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Invalid provider identifier provided'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Invalid service identifier provided'); } // retrieve service return $this->fileManager->serviceFetch($tenantId, $userId, $data['provider'], $data['identifier']); } // ==================== Collection Operations ==================== private function collectionList(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Invalid provider identifier provided'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Invalid service identifier provided'); } $provider = $data['provider']; $service = $data['service']; $location = $data['location'] ?? null; $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; return $this->fileManager->collectionList( $tenantId, $userId, $provider, $service, $location, $filter, $sort ); } private function collectionExtant(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } return [ 'extant' => $this->fileManager->collectionExtant( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'] ) ]; } private function collectionFetch(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } return $this->fileManager->collectionFetch( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'] ); } private function collectionCreate(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['data'])) { throw new InvalidArgumentException('Missing required parameter: data'); } $location = $data['location'] ?? null; $options = $data['options'] ?? []; return $this->fileManager->collectionCreate( $tenantId, $userId, $data['provider'], $data['service'], $location, $data['data'], $options ); } private function collectionModify(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } if (!isset($data['data'])) { throw new InvalidArgumentException('Missing required parameter: data'); } return $this->fileManager->collectionModify( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'], $data['data'] ); } private function collectionDestroy(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } return [ 'success' => $this->fileManager->collectionDestroy( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'] ) ]; } private function collectionCopy(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $location = $data['location'] ?? null; return $this->fileManager->collectionCopy( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'], $location ); } private function collectionMove(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $location = $data['location'] ?? null; return $this->fileManager->collectionMove( $tenantId, $userId, $data['provider'], $data['service'], $data['identifier'], $location ); } // ==================== Entity Operations ==================== private function entityList(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Invalid provider identifier provided'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Invalid service identifier provided'); } if (!isset($data['collection'])) { throw new InvalidArgumentException('Missing required parameter: collection'); } $provider = $data['provider']; $service = $data['service']; $collection = $data['collection']; $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; $range = $data['range'] ?? null; return $this->fileManager->entityList( $tenantId, $userId, $provider, $service, $collection, $filter, $sort, $range ); } private function entityDelta(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['collection'])) { throw new InvalidArgumentException('Missing required parameter: collection'); } if (!isset($data['signature']) || !is_string($data['signature'])) { throw new InvalidArgumentException('Missing required parameter: signature'); } $detail = $data['detail'] ?? 'ids'; return $this->fileManager->entityDelta( $tenantId, $userId, $data['provider'], $data['service'], $data['collection'], $data['signature'], $detail ); } private function entityExtant(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['collection'])) { throw new InvalidArgumentException('Missing required parameter: collection'); } if (!isset($data['identifiers']) || !is_array($data['identifiers'])) { throw new InvalidArgumentException('Missing required parameter: identifiers'); } return $this->fileManager->entityExtant( $tenantId, $userId, $data['provider'], $data['service'], $data['collection'], $data['identifiers'] ); } private function entityFetch(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['collection'])) { throw new InvalidArgumentException('Missing required parameter: collection'); } if (!isset($data['identifiers']) || !is_array($data['identifiers'])) { throw new InvalidArgumentException('Missing required parameter: identifiers'); } return $this->fileManager->entityFetch( $tenantId, $userId, $data['provider'], $data['service'], $data['collection'], $data['identifiers'] ); } private function entityRead(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['collection'])) { throw new InvalidArgumentException('Missing required parameter: collection'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $content = $this->fileManager->entityRead( $tenantId, $userId, $data['provider'], $data['service'], $data['collection'], $data['identifier'] ); return [ 'content' => $content !== null ? base64_encode($content) : null, 'encoding' => 'base64' ]; } private function entityCreate(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['data'])) { throw new InvalidArgumentException('Missing required parameter: data'); } $collection = $data['collection'] ?? null; $options = $data['options'] ?? []; return $this->fileManager->entityCreate( $tenantId, $userId, $data['provider'], $data['service'], $collection, $data['data'], $options ); } private function entityModify(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } if (!isset($data['data'])) { throw new InvalidArgumentException('Missing required parameter: data'); } $collection = $data['collection'] ?? null; return $this->fileManager->entityModify( $tenantId, $userId, $data['provider'], $data['service'], $collection, $data['identifier'], $data['data'] ); } private function entityDestroy(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $collection = $data['collection'] ?? null; return [ 'success' => $this->fileManager->entityDestroy( $tenantId, $userId, $data['provider'], $data['service'], $collection, $data['identifier'] ) ]; } private function entityCopy(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $collection = $data['collection'] ?? null; $destination = $data['destination'] ?? null; return $this->fileManager->entityCopy( $tenantId, $userId, $data['provider'], $data['service'], $collection, $data['identifier'], $destination ); } private function entityMove(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } $collection = $data['collection'] ?? null; $destination = $data['destination'] ?? null; return $this->fileManager->entityMove( $tenantId, $userId, $data['provider'], $data['service'], $collection, $data['identifier'], $destination ); } private function entityWrite(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['identifier'])) { throw new InvalidArgumentException('Missing required parameter: identifier'); } if (!isset($data['content'])) { throw new InvalidArgumentException('Missing required parameter: content'); } // Decode content if base64 encoded $content = $data['content']; if (isset($data['encoding']) && $data['encoding'] === 'base64') { $content = base64_decode($content); if ($content === false) { throw new InvalidArgumentException('Invalid base64 encoded content'); } } $bytesWritten = $this->fileManager->entityWrite( $tenantId, $userId, $data['provider'], $data['service'], $data['collection'], $data['identifier'], $content ); return [ 'bytesWritten' => $bytesWritten ]; } // ==================== Node Operations (Unified/Recursive) ==================== private function nodeList(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Invalid provider identifier provided'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Invalid service identifier provided'); } $provider = $data['provider']; $service = $data['service']; $location = $data['location'] ?? null; $recursive = $data['recursive'] ?? false; $filter = $data['filter'] ?? null; $sort = $data['sort'] ?? null; $range = $data['range'] ?? null; return $this->fileManager->nodeList( $tenantId, $userId, $provider, $service, $location, $recursive, $filter, $sort, $range ); } private function nodeDelta(string $tenantId, string $userId, array $data = []): mixed { if (!isset($data['provider']) || !is_string($data['provider'])) { throw new InvalidArgumentException('Missing required parameter: provider'); } if (!isset($data['service'])) { throw new InvalidArgumentException('Missing required parameter: service'); } if (!isset($data['signature']) || !is_string($data['signature'])) { throw new InvalidArgumentException('Missing required parameter: signature'); } $location = $data['location'] ?? null; $recursive = $data['recursive'] ?? false; $detail = $data['detail'] ?? 'ids'; return $this->fileManager->nodeDelta( $tenantId, $userId, $data['provider'], $data['service'], $location, $data['signature'], $recursive, $detail ); } }