feat: mail entity download
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -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;
|
||||
@@ -31,12 +32,6 @@ use KTXM\MailManager\Manager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Default Controller - Unified Mail API
|
||||
*
|
||||
* Handles all mail operations in JMAP-style API pattern.
|
||||
* Supports both single operations and batches with result references.
|
||||
*/
|
||||
class DefaultController extends ControllerAbstract {
|
||||
|
||||
private const ERR_MISSING_PROVIDER = 'Missing parameter: provider';
|
||||
@@ -168,6 +163,7 @@ class DefaultController extends ControllerAbstract {
|
||||
'entity.move' => $this->entityMove($tenantId, $userId, $data),
|
||||
'entity.copy' => throw new InvalidArgumentException('Operation not implemented: ' . $operation),
|
||||
'entity.transmit' => $this->entityTransmit($tenantId, $userId, $data),
|
||||
'entity.download' => $this->entityDownload($tenantId, $userId, $data),
|
||||
|
||||
default => throw new InvalidArgumentException(self::ERR_INVALID_OPERATION . $operation)
|
||||
};
|
||||
@@ -558,14 +554,14 @@ class DefaultController extends ControllerAbstract {
|
||||
|
||||
if (is_bool($result)) {
|
||||
return [
|
||||
'outcome' => 'deleted'
|
||||
'disposition' => 'deleted'
|
||||
];
|
||||
}
|
||||
|
||||
if ($result instanceof JsonSerializable) {
|
||||
return [
|
||||
'outcome' => 'moved',
|
||||
'data' => $result
|
||||
'disposition' => 'moved',
|
||||
'mutation' => $result
|
||||
];
|
||||
}
|
||||
|
||||
@@ -773,21 +769,21 @@ class DefaultController extends ControllerAbstract {
|
||||
}
|
||||
|
||||
private function entityDelete(string $tenantId, string $userId, array $data): mixed {
|
||||
if (!isset($data['sources'])) {
|
||||
throw new InvalidArgumentException(self::ERR_MISSING_SOURCES);
|
||||
if (!isset($data['targets'])) {
|
||||
throw new InvalidArgumentException(self::ERR_MISSING_TARGETS);
|
||||
}
|
||||
if (!is_array($data['sources'])) {
|
||||
throw new InvalidArgumentException(self::ERR_INVALID_SOURCES);
|
||||
if (!is_array($data['targets'])) {
|
||||
throw new InvalidArgumentException(self::ERR_INVALID_TARGETS);
|
||||
}
|
||||
|
||||
$sources = ResourceIdentifiers::fromArray($data['sources']);
|
||||
foreach ($sources as $source) {
|
||||
if (!$source instanceof EntityIdentifier) {
|
||||
throw new InvalidArgumentException('Invalid parameter: sources must contain provider:service:collection:entity identifiers');
|
||||
$targets = ResourceIdentifiers::fromArray($data['targets']);
|
||||
foreach ($targets as $target) {
|
||||
if (!$target instanceof EntityIdentifier) {
|
||||
throw new InvalidArgumentException('Invalid parameter: targets must contain provider:service:collection:entity identifiers');
|
||||
}
|
||||
}
|
||||
|
||||
return $this->mailManager->entityDelete($tenantId, $userId, ...$sources->all());
|
||||
return $this->mailManager->entityDelete($tenantId, $userId, ...$targets->all());
|
||||
}
|
||||
|
||||
private function entityPatch(string $tenantId, string $userId, array $data): mixed {
|
||||
@@ -868,4 +864,46 @@ class DefaultController extends ControllerAbstract {
|
||||
return ['jobId' => $jobId];
|
||||
}
|
||||
|
||||
private function entityDownload(string $tenantId, string $userId, array $data): mixed {
|
||||
if (!isset($data['target'])) {
|
||||
throw new InvalidArgumentException(self::ERR_MISSING_TARGET);
|
||||
}
|
||||
if (!is_string($data['target'])) {
|
||||
throw new InvalidArgumentException(self::ERR_INVALID_IDENTIFIER);
|
||||
}
|
||||
|
||||
// 'part' is optional — null means full RFC 822 message download
|
||||
$part = isset($data['part']) && is_array($data['part']) ? $data['part'] : null;
|
||||
|
||||
$target = ResourceIdentifier::fromString($data['target']);
|
||||
$logger = $this->logger;
|
||||
|
||||
$result = $this->mailManager->entityDownload($tenantId, $userId, $target, $part);
|
||||
|
||||
$filename = $result->filename();
|
||||
$asciiFilename = preg_replace('/[^\x20-\x7E]|[\\\\"]/', '_', $filename);
|
||||
$encodedFilename = rawurlencode($filename);
|
||||
$disposition = sprintf(
|
||||
'attachment; filename="%s"; filename*=UTF-8\'\'%s',
|
||||
$asciiFilename,
|
||||
$encodedFilename,
|
||||
);
|
||||
|
||||
$responseGenerator = (static function () use ($result, $logger): \Generator {
|
||||
try {
|
||||
yield from $result->stream();
|
||||
} catch (\Throwable $t) {
|
||||
$logger->error('Error streaming entity download', ['exception' => $t]);
|
||||
// Headers already sent — cannot change status code; stop output cleanly
|
||||
}
|
||||
})();
|
||||
|
||||
return new StreamedResponse($responseGenerator, 200, [
|
||||
'Content-Disposition' => $disposition,
|
||||
'Content-Type' => $result->mimeType(),
|
||||
'Content-Transfer-Encoding' => 'binary',
|
||||
'Cache-Control' => 'no-store',
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ use KTXF\Mail\Service\ServiceCollectionMutableInterface;
|
||||
use KTXF\Mail\Service\ServiceConfigurableInterface;
|
||||
use KTXF\Mail\Service\ServiceEntityMutableInterface;
|
||||
use KTXF\Mail\Service\ServiceMutableInterface;
|
||||
use KTXF\Resource\BinaryResource;
|
||||
use KTXF\Resource\Filter\IFilter;
|
||||
use KTXF\Resource\Identifier\CollectionIdentifier;
|
||||
use KTXF\Resource\Identifier\EntityIdentifier;
|
||||
@@ -951,6 +952,13 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
public function entityDownload(string $tenantId, string $userId, EntityIdentifier $targetEntity, array|null $targetPart): BinaryResource {
|
||||
// retrieve service
|
||||
$service = $this->serviceFetch($tenantId, $userId, $targetEntity->provider(), $targetEntity->service());
|
||||
// download entity
|
||||
return $service->entityDownload($targetEntity, $targetPart);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if messages exist
|
||||
*
|
||||
@@ -1294,5 +1302,4 @@ class Manager {
|
||||
return $operationOutcome;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user