refactor: documents interfaces
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -15,6 +15,11 @@ use KTXC\Http\Response\StreamedResponse;
|
||||
use KTXC\SessionIdentity;
|
||||
use KTXC\SessionTenant;
|
||||
use KTXF\Controller\ControllerAbstract;
|
||||
use KTXF\Documents\Collection\CollectionBaseInterface;
|
||||
use KTXF\Documents\Entity\EntityBaseInterface;
|
||||
use KTXF\Resource\Identifier\CollectionIdentifier;
|
||||
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||
use KTXF\Resource\Identifier\ResourceIdentifiers;
|
||||
use KTXF\Routing\Attributes\AuthenticatedRoute;
|
||||
use KTXM\DocumentsManager\Manager;
|
||||
use KTXM\DocumentsManager\Transfer\StreamingZip;
|
||||
@@ -23,7 +28,7 @@ use Throwable;
|
||||
|
||||
/**
|
||||
* Controller for file transfers (downloads and uploads)
|
||||
*
|
||||
*
|
||||
* Handles binary file transfers that don't fit the JSON API pattern:
|
||||
* - Single file downloads (streamed)
|
||||
* - Multi-file downloads as ZIP (streamed)
|
||||
@@ -42,7 +47,7 @@ class TransferController extends ControllerAbstract
|
||||
|
||||
/**
|
||||
* Download a single file
|
||||
*
|
||||
*
|
||||
* GET /download/entity/{provider}/{service}/{collection}/{identifier}
|
||||
*/
|
||||
#[AuthenticatedRoute(
|
||||
@@ -55,15 +60,11 @@ class TransferController extends ControllerAbstract
|
||||
$userId = $this->userIdentity->identifier();
|
||||
|
||||
try {
|
||||
$target = new EntityIdentifier($provider, $service, $collection, $identifier);
|
||||
|
||||
// Fetch entity metadata
|
||||
$entities = $this->manager->entityFetch(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$collection,
|
||||
[$identifier]
|
||||
);
|
||||
/** @var EntityBaseInterface[] $entities */
|
||||
$entities = $this->manager->entityFetchBulk($tenantId, $userId, $target);
|
||||
|
||||
if (empty($entities) || !isset($entities[$identifier])) {
|
||||
return new JsonResponse([
|
||||
@@ -73,16 +74,9 @@ class TransferController extends ControllerAbstract
|
||||
}
|
||||
|
||||
$entity = $entities[$identifier];
|
||||
|
||||
|
||||
// Get the stream
|
||||
$stream = $this->manager->entityReadStream(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$collection,
|
||||
$identifier
|
||||
);
|
||||
$stream = $this->manager->entityReadStream($tenantId, $userId, $target);
|
||||
|
||||
if ($stream === null) {
|
||||
return new JsonResponse([
|
||||
@@ -114,7 +108,7 @@ class TransferController extends ControllerAbstract
|
||||
if ($size > 0) {
|
||||
$response->headers->set('Content-Length', (string) $size);
|
||||
}
|
||||
$response->headers->set('Content-Disposition',
|
||||
$response->headers->set('Content-Disposition',
|
||||
$response->headers->makeDisposition('attachment', $filename, $this->asciiFallback($filename))
|
||||
);
|
||||
$response->headers->set('Cache-Control', 'private, no-cache');
|
||||
@@ -132,7 +126,7 @@ class TransferController extends ControllerAbstract
|
||||
|
||||
/**
|
||||
* Download multiple files as a ZIP archive
|
||||
*
|
||||
*
|
||||
* GET /download/archive?provider=...&service=...&ids[]=...&ids[]=...
|
||||
*/
|
||||
#[AuthenticatedRoute(
|
||||
@@ -179,10 +173,7 @@ class TransferController extends ControllerAbstract
|
||||
$stream = $this->manager->entityReadStream(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$file['collection'],
|
||||
$file['id']
|
||||
new EntityIdentifier($provider, $service, (string)$file['collection'], (string)$file['id'])
|
||||
);
|
||||
|
||||
if ($stream !== null) {
|
||||
@@ -221,7 +212,7 @@ class TransferController extends ControllerAbstract
|
||||
|
||||
/**
|
||||
* Download a collection (folder) as a ZIP archive with structure preserved
|
||||
*
|
||||
*
|
||||
* GET /download/collection/{provider}/{service}/{identifier}
|
||||
*/
|
||||
#[AuthenticatedRoute(
|
||||
@@ -239,9 +230,7 @@ class TransferController extends ControllerAbstract
|
||||
$collection = $this->manager->collectionFetch(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$identifier
|
||||
new CollectionIdentifier($provider, $service, $identifier)
|
||||
);
|
||||
|
||||
if ($collection === null) {
|
||||
@@ -251,7 +240,7 @@ class TransferController extends ControllerAbstract
|
||||
], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$folderName = $collection->getLabel() ?? 'folder';
|
||||
$folderName = $collection->getProperties()->getLabel() ?? 'folder';
|
||||
$archiveName = $this->sanitizeFilename($folderName) . '.zip';
|
||||
|
||||
// Build recursive file list
|
||||
@@ -275,10 +264,7 @@ class TransferController extends ControllerAbstract
|
||||
$stream = $this->manager->entityReadStream(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$file['collection'],
|
||||
$file['id']
|
||||
new EntityIdentifier($provider, $service, (string)$file['collection'], (string)$file['id'])
|
||||
);
|
||||
|
||||
if ($stream !== null) {
|
||||
@@ -325,13 +311,10 @@ class TransferController extends ControllerAbstract
|
||||
// Try as entity first
|
||||
if ($collection !== null) {
|
||||
/** @var EntityBaseInterface[] $entities */
|
||||
$entities = $this->manager->entityFetch(
|
||||
$entities = $this->manager->entityFetchBulk(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$collection,
|
||||
[$id]
|
||||
new EntityIdentifier($provider, $service, $collection, (string)$id)
|
||||
);
|
||||
|
||||
if (!empty($entities) && isset($entities[$id])) {
|
||||
@@ -340,8 +323,8 @@ class TransferController extends ControllerAbstract
|
||||
'type' => 'file',
|
||||
'id' => $id,
|
||||
'collection' => $collection,
|
||||
'path' => $entity->getLabel() ?? $id,
|
||||
'modTime' => $entity->modifiedOn()?->getTimestamp(),
|
||||
'path' => $entity->getProperties()->getLabel() ?? $id,
|
||||
'modTime' => $entity->modified()?->getTimestamp(),
|
||||
];
|
||||
continue;
|
||||
}
|
||||
@@ -352,19 +335,17 @@ class TransferController extends ControllerAbstract
|
||||
$collectionNode = $this->manager->collectionFetch(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$id
|
||||
new CollectionIdentifier($provider, $service, (string)$id)
|
||||
);
|
||||
|
||||
if ($collectionNode !== null) {
|
||||
$folderName = $collectionNode->getLabel() ?? $id;
|
||||
$folderName = $collectionNode->getProperties()->getLabel() ?? $id;
|
||||
$subFiles = $this->resolveCollectionContents(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$id,
|
||||
(string)$id,
|
||||
$folderName
|
||||
);
|
||||
$files = array_merge($files, $subFiles);
|
||||
@@ -407,17 +388,13 @@ class TransferController extends ControllerAbstract
|
||||
];
|
||||
}
|
||||
|
||||
// Get all nodes in this collection using nodeList with recursive=false
|
||||
// We handle recursion ourselves to build proper paths
|
||||
// List immediate child collections and entities of this collection;
|
||||
// recursion is handled here to build proper archive paths
|
||||
$sources = new ResourceIdentifiers();
|
||||
$sources->add(new CollectionIdentifier($provider, $service, $collectionId));
|
||||
try {
|
||||
$nodes = $this->manager->nodeList(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
$collectionId,
|
||||
false // Not recursive - we handle it ourselves
|
||||
);
|
||||
$collections = $this->manager->collectionList($tenantId, $userId, $sources)[$provider][$service] ?? [];
|
||||
$entities = $this->manager->entityListBulk($tenantId, $userId, $sources)[$provider][$service][$collectionId] ?? [];
|
||||
} catch (Throwable $e) {
|
||||
$this->logger->warning('Failed to list collection contents', [
|
||||
'collection' => $collectionId,
|
||||
@@ -426,34 +403,35 @@ class TransferController extends ControllerAbstract
|
||||
return $files;
|
||||
}
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$nodeName = $node->getLabel() ?? (string) $node->id();
|
||||
/** @var CollectionBaseInterface $node */
|
||||
foreach ($collections as $node) {
|
||||
$nodeName = $node->getProperties()->getLabel() ?? (string) $node->identifier();
|
||||
$nodePath = $basePath !== '' ? $basePath . '/' . $nodeName : $nodeName;
|
||||
// Recursively get contents of sub-collection
|
||||
$subFiles = $this->resolveCollectionContents(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
(string) $node->identifier(),
|
||||
$nodePath,
|
||||
$depth + 1,
|
||||
$maxDepth
|
||||
);
|
||||
$files = array_merge($files, $subFiles);
|
||||
}
|
||||
|
||||
if ($node->isCollection()) {
|
||||
// Recursively get contents of sub-collection
|
||||
$subFiles = $this->resolveCollectionContents(
|
||||
$tenantId,
|
||||
$userId,
|
||||
$provider,
|
||||
$service,
|
||||
(string) $node->id(),
|
||||
$nodePath,
|
||||
$depth + 1,
|
||||
$maxDepth
|
||||
);
|
||||
$files = array_merge($files, $subFiles);
|
||||
} else {
|
||||
// It's an entity (file)
|
||||
/** @var INodeEntityBase $node */
|
||||
$files[] = [
|
||||
'type' => 'file',
|
||||
'id' => (string) $node->id(),
|
||||
'collection' => $collectionId,
|
||||
'path' => $nodePath,
|
||||
'modTime' => $node->modifiedOn()?->getTimestamp(),
|
||||
];
|
||||
}
|
||||
/** @var EntityBaseInterface $node */
|
||||
foreach ($entities as $node) {
|
||||
$nodeName = $node->getProperties()->getLabel() ?? (string) $node->identifier();
|
||||
$nodePath = $basePath !== '' ? $basePath . '/' . $nodeName : $nodeName;
|
||||
$files[] = [
|
||||
'type' => 'file',
|
||||
'id' => (string) $node->identifier(),
|
||||
'collection' => $collectionId,
|
||||
'path' => $nodePath,
|
||||
'modTime' => $node->modified()?->getTimestamp(),
|
||||
];
|
||||
}
|
||||
|
||||
return $files;
|
||||
@@ -467,11 +445,11 @@ class TransferController extends ControllerAbstract
|
||||
// Remove or replace problematic characters
|
||||
$filename = preg_replace('/[<>:"\/\\|?*\x00-\x1F]/', '_', $filename);
|
||||
$filename = trim($filename, '. ');
|
||||
|
||||
|
||||
if ($filename === '') {
|
||||
$filename = 'download';
|
||||
}
|
||||
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user