feat: 2 way conversion and export

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-07-17 19:13:08 -04:00
parent 44ca40cc6e
commit 3d1a07e0ad
6 changed files with 843 additions and 296 deletions
+43
View File
@@ -13,6 +13,7 @@ use InvalidArgumentException;
use KTXC\Http\Response\JsonResponse;
use KTXC\Http\Response\Response;
use KTXC\Http\Response\StreamedNdJsonResponse;
use KTXC\Http\Response\StreamedResponse;
use KTXC\SessionIdentity;
use KTXC\SessionTenant;
use KTXF\Controller\ControllerAbstract;
@@ -23,6 +24,7 @@ use KTXF\Resource\Identifier\ResourceIdentifier;
use KTXF\Resource\Identifier\ResourceIdentifiers;
use KTXF\Resource\Identifier\ServiceIdentifier;
use KTXF\Routing\Attributes\AuthenticatedRoute;
use KTXM\ChronoManager\Export\ExportService;
use KTXM\ChronoManager\Import\ImportOptions;
use KTXM\ChronoManager\Import\ImportService;
use KTXM\ChronoManager\Manager;
@@ -53,6 +55,7 @@ class DefaultController extends ControllerAbstract {
private readonly SessionIdentity $userIdentity,
private readonly Manager $manager,
private readonly ImportService $importService,
private readonly ExportService $exportService,
private readonly LoggerInterface $logger,
) {}
@@ -156,6 +159,7 @@ class DefaultController extends ControllerAbstract {
'entity.delete' => $this->entityDelete($tenantId, $userId, $data),
'entity.move' => $this->entityMove($tenantId, $userId, $data),
'entity.import' => $this->entityImport($tenantId, $userId, $data, $version, $transaction),
'entity.export' => $this->entityExport($tenantId, $userId, $data),
default => throw new InvalidArgumentException(self::ERR_INVALID_OPERATION . $operation)
};
@@ -607,6 +611,45 @@ class DefaultController extends ControllerAbstract {
return new StreamedNdJsonResponse($response, 1, 200);
}
/**
* Export calendar entities as a streamed iCalendar document.
*
* Request data: { sources: ["provider:service:collection", "provider:service:collection:entity", ...], filename?: "calendar.ics" }
*/
private function entityExport(string $tenantId, string $userId, array $data): StreamedResponse {
if (!isset($data['sources'])) {
throw new InvalidArgumentException(self::ERR_MISSING_SOURCES);
}
if (!is_array($data['sources']) || $data['sources'] === []) {
throw new InvalidArgumentException(self::ERR_INVALID_SOURCES);
}
$sources = ResourceIdentifiers::fromArray($data['sources']);
foreach ($sources as $source) {
if (!$source instanceof CollectionIdentifier && !$source instanceof EntityIdentifier) {
throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection or provider:service:collection:entity identifiers');
}
}
$filename = 'export.ics';
if (isset($data['filename']) && is_string($data['filename'])) {
$sanitized = preg_replace('/[^A-Za-z0-9._-]+/', '_', trim($data['filename']));
if ($sanitized !== '' && $sanitized !== null) {
$filename = str_ends_with(strtolower($sanitized), '.ics') ? $sanitized : $sanitized . '.ics';
}
}
return new StreamedResponse(
$this->exportService->export($sources, $tenantId, $userId),
200,
[
'Content-Type' => 'text/calendar; charset=utf-8',
'Content-Disposition' => 'attachment; filename="' . $filename . '"',
],
);
}
/**
* Wrap a generator of JsonSerializable domain objects in the canonical NDJSON
* stream envelope shared by every streaming operation: