refactor: front end

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-03-28 12:47:21 -04:00
parent 28e5cce23a
commit ccb781f933
38 changed files with 4909 additions and 2897 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -7,20 +7,17 @@ declare(strict_types=1);
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXM\FileManager\Controllers;
namespace KTXM\DocumentsManager\Controllers;
use InvalidArgumentException;
use KTXC\Http\Response\JsonResponse;
use KTXC\Http\Response\Response;
use KTXC\Http\Response\StreamedResponse;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXF\Controller\ControllerAbstract;
use KTXF\Files\Node\INodeCollectionBase;
use KTXF\Files\Node\INodeEntityBase;
use KTXF\Routing\Attributes\AuthenticatedRoute;
use KTXM\FileManager\Manager;
use KTXM\FileManager\Transfer\StreamingZip;
use KTXM\DocumentsManager\Manager;
use KTXM\DocumentsManager\Transfer\StreamingZip;
use Psr\Log\LoggerInterface;
use Throwable;
@@ -39,7 +36,7 @@ class TransferController extends ControllerAbstract
public function __construct(
private readonly SessionTenant $tenantIdentity,
private readonly SessionIdentity $userIdentity,
private Manager $fileManager,
private readonly Manager $manager,
private readonly LoggerInterface $logger
) {}
@@ -50,21 +47,16 @@ class TransferController extends ControllerAbstract
*/
#[AuthenticatedRoute(
'/download/entity/{provider}/{service}/{collection}/{identifier}',
name: 'filemanager.download.entity',
name: 'document_manager.download.entity',
methods: ['GET']
)]
public function downloadEntity(
string $provider,
string $service,
string $collection,
string $identifier
): Response {
public function downloadEntity(string $provider, string $service, string $collection, string $identifier): Response {
$tenantId = $this->tenantIdentity->identifier();
$userId = $this->userIdentity->identifier();
try {
// Fetch entity metadata
$entities = $this->fileManager->entityFetch(
$entities = $this->manager->entityFetch(
$tenantId,
$userId,
$provider,
@@ -80,11 +72,10 @@ class TransferController extends ControllerAbstract
], Response::HTTP_NOT_FOUND);
}
/** @var INodeEntityBase $entity */
$entity = $entities[$identifier];
// Get the stream
$stream = $this->fileManager->entityReadStream(
$stream = $this->manager->entityReadStream(
$tenantId,
$userId,
$provider,
@@ -100,22 +91,29 @@ class TransferController extends ControllerAbstract
], Response::HTTP_NOT_FOUND);
}
$filename = $entity->getLabel() ?? 'download';
$mime = $entity->getMime() ?? 'application/octet-stream';
$size = $entity->size();
$filename = $entity->getProperties()->getLabel() ?? 'download';
$mime = $entity->getProperties()->getMime() ?? 'application/octet-stream';
$size = $entity->getProperties()->size();
// Create streamed response
$response = new StreamedResponse(function () use ($stream) {
while (!feof($stream)) {
echo fread($stream, 65536);
@ob_flush();
flush();
try {
while (!feof($stream)) {
echo fread($stream, 65536);
@ob_flush();
flush();
}
} finally {
fclose($stream);
}
fclose($stream);
});
$response->headers->set('Content-Type', $mime);
$response->headers->set('Content-Length', (string) $size);
// Only advertise Content-Length when metadata is non-zero; a zero value
// would cause clients to believe the file is empty and discard the body.
if ($size > 0) {
$response->headers->set('Content-Length', (string) $size);
}
$response->headers->set('Content-Disposition',
$response->headers->makeDisposition('attachment', $filename, $this->asciiFallback($filename))
);
@@ -139,16 +137,10 @@ class TransferController extends ControllerAbstract
*/
#[AuthenticatedRoute(
'/download/archive',
name: 'filemanager.download.archive',
name: 'manager.download.archive',
methods: ['GET']
)]
public function downloadArchive(
string $provider,
string $service,
array $ids = [],
string $collection = null,
string $name = 'download'
): Response {
public function downloadArchive(string $provider, string $service, array $ids = [], ?string $collection = null, string $name = 'download'): Response {
$tenantId = $this->tenantIdentity->identifier();
$userId = $this->userIdentity->identifier();
@@ -184,7 +176,7 @@ class TransferController extends ControllerAbstract
$zip = new StreamingZip(null, false); // No compression for speed
foreach ($files as $file) {
$stream = $this->fileManager->entityReadStream(
$stream = $this->manager->entityReadStream(
$tenantId,
$userId,
$provider,
@@ -194,12 +186,15 @@ class TransferController extends ControllerAbstract
);
if ($stream !== null) {
$zip->addFileFromStream(
$file['path'],
$stream,
$file['modTime'] ?? null
);
fclose($stream);
try {
$zip->addFileFromStream(
$file['path'],
$stream,
$file['modTime'] ?? null
);
} finally {
fclose($stream);
}
}
}
@@ -231,20 +226,17 @@ class TransferController extends ControllerAbstract
*/
#[AuthenticatedRoute(
'/download/collection/{provider}/{service}/{identifier}',
name: 'filemanager.download.collection',
name: 'manager.download.collection',
methods: ['GET']
)]
public function downloadCollection(
string $provider,
string $service,
string $identifier
): Response {
public function downloadCollection(string $provider, string $service, string $identifier): Response {
$tenantId = $this->tenantIdentity->identifier();
$userId = $this->userIdentity->identifier();
try {
// Fetch collection metadata
$collection = $this->fileManager->collectionFetch(
/** @var CollectionBaseInterface|null $collection */
$collection = $this->manager->collectionFetch(
$tenantId,
$userId,
$provider,
@@ -280,7 +272,7 @@ class TransferController extends ControllerAbstract
if ($file['type'] === 'directory') {
$zip->addDirectory($file['path'], $file['modTime'] ?? null);
} else {
$stream = $this->fileManager->entityReadStream(
$stream = $this->manager->entityReadStream(
$tenantId,
$userId,
$provider,
@@ -290,12 +282,15 @@ class TransferController extends ControllerAbstract
);
if ($stream !== null) {
$zip->addFileFromStream(
$file['path'],
$stream,
$file['modTime'] ?? null
);
fclose($stream);
try {
$zip->addFileFromStream(
$file['path'],
$stream,
$file['modTime'] ?? null
);
} finally {
fclose($stream);
}
}
}
}
@@ -323,20 +318,14 @@ class TransferController extends ControllerAbstract
/**
* Resolve a list of entity/collection IDs into a flat file list for archiving
*/
private function resolveFilesForArchive(
string $tenantId,
string $userId,
string $provider,
string $service,
?string $collection,
array $ids
): array {
private function resolveFilesForArchive(string $tenantId, string $userId, string $provider, string $service, ?string $collection, array $ids): array {
$files = [];
foreach ($ids as $id) {
// Try as entity first
if ($collection !== null) {
$entities = $this->fileManager->entityFetch(
/** @var EntityBaseInterface[] $entities */
$entities = $this->manager->entityFetch(
$tenantId,
$userId,
$provider,
@@ -346,7 +335,6 @@ class TransferController extends ControllerAbstract
);
if (!empty($entities) && isset($entities[$id])) {
/** @var INodeEntityBase $entity */
$entity = $entities[$id];
$files[] = [
'type' => 'file',
@@ -360,7 +348,8 @@ class TransferController extends ControllerAbstract
}
// Try as collection (folder)
$collectionNode = $this->fileManager->collectionFetch(
/** @var CollectionBaseInterface|null $collectionNode */
$collectionNode = $this->manager->collectionFetch(
$tenantId,
$userId,
$provider,
@@ -394,10 +383,21 @@ class TransferController extends ControllerAbstract
string $provider,
string $service,
string $collectionId,
string $basePath
string $basePath,
int $depth = 0,
int $maxDepth = 20
): array {
$files = [];
// Guard against runaway recursion on pathologically deep trees
if ($depth > $maxDepth) {
$this->logger->warning('Max recursion depth reached, skipping deeper contents', [
'collection' => $collectionId,
'depth' => $depth,
]);
return $files;
}
// Add directory entry if we have a path
if ($basePath !== '') {
$files[] = [
@@ -410,7 +410,7 @@ class TransferController extends ControllerAbstract
// Get all nodes in this collection using nodeList with recursive=false
// We handle recursion ourselves to build proper paths
try {
$nodes = $this->fileManager->nodeList(
$nodes = $this->manager->nodeList(
$tenantId,
$userId,
$provider,
@@ -438,7 +438,9 @@ class TransferController extends ControllerAbstract
$provider,
$service,
(string) $node->id(),
$nodePath
$nodePath,
$depth + 1,
$maxDepth
);
$files = array_merge($files, $subFiles);
} else {

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
<?php
namespace KTXM\FileManager;
namespace KTXM\DocumentsManager;
use KTXF\Module\ModuleBrowserInterface;
use KTXF\Module\ModuleInstanceAbstract;
@@ -16,12 +16,12 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
public function handle(): string
{
return 'file_manager';
return 'documents_manager';
}
public function label(): string
{
return 'File Manager';
return 'Documents Manager';
}
public function author(): string
@@ -31,7 +31,7 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
public function description(): string
{
return 'File management module for Ktrix - provides file and folder management functionalities';
return 'Documents management module for Ktrix - provides document and folder management functionalities';
}
public function version(): string
@@ -42,10 +42,10 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
public function permissions(): array
{
return [
'file_manager' => [
'label' => 'Access File Manager',
'description' => 'View and access the file manager module',
'group' => 'File Management'
'documents_manager' => [
'label' => 'Access Documents Manager',
'description' => 'View and access the documents manager module',
'group' => 'Document Management'
],
];
}
@@ -53,7 +53,7 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
public function registerBI(): array {
return [
'handle' => $this->handle(),
'namespace' => 'FileManager',
'namespace' => 'DocumentsManager',
'version' => $this->version(),
'label' => $this->label(),
'author' => $this->author(),

View File

@@ -7,7 +7,7 @@ declare(strict_types=1);
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace KTXM\FileManager\Transfer;
namespace KTXM\DocumentsManager\Transfer;
/**
* Native PHP streaming ZIP archive generator