feat: blobs fetch
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -162,6 +162,7 @@ class DefaultController extends ControllerAbstract {
|
|||||||
'entity.copy' => throw new InvalidArgumentException('Operation not implemented: ' . $operation),
|
'entity.copy' => throw new InvalidArgumentException('Operation not implemented: ' . $operation),
|
||||||
'entity.submit' => $this->entitySubmit($tenantId, $userId, $data),
|
'entity.submit' => $this->entitySubmit($tenantId, $userId, $data),
|
||||||
'entity.download' => $this->entityDownload($tenantId, $userId, $data),
|
'entity.download' => $this->entityDownload($tenantId, $userId, $data),
|
||||||
|
'entity.blobs' => $this->entityBlobs($tenantId, $userId, $data),
|
||||||
|
|
||||||
default => throw new InvalidArgumentException(self::ERR_INVALID_OPERATION . $operation)
|
default => throw new InvalidArgumentException(self::ERR_INVALID_OPERATION . $operation)
|
||||||
};
|
};
|
||||||
@@ -895,5 +896,43 @@ class DefaultController extends ControllerAbstract {
|
|||||||
'Cache-Control' => 'no-store',
|
'Cache-Control' => 'no-store',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function entityBlobs(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);
|
||||||
|
}
|
||||||
|
if (!isset($data['parts']) || !is_array($data['parts']) || $data['parts'] === []) {
|
||||||
|
throw new InvalidArgumentException('At least one part selector is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
$target = ResourceIdentifier::fromString($data['target']);
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
foreach ($data['parts'] as $part) {
|
||||||
|
if (!is_array($part)) {
|
||||||
|
throw new InvalidArgumentException('Invalid part selector');
|
||||||
|
}
|
||||||
|
|
||||||
|
$resource = $this->mailManager->entityDownload($tenantId, $userId, $target, $part);
|
||||||
|
|
||||||
|
$bytes = '';
|
||||||
|
foreach ($resource->stream() as $chunk) {
|
||||||
|
$bytes .= $chunk;
|
||||||
|
}
|
||||||
|
|
||||||
|
$results[] = [
|
||||||
|
'source' => $data['target'],
|
||||||
|
'part' => $part,
|
||||||
|
'mime' => $resource->mimeType(),
|
||||||
|
'filename' => $resource->filename(),
|
||||||
|
'bytes' => base64_encode($bytes),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ import type {
|
|||||||
EntityPatchResponse,
|
EntityPatchResponse,
|
||||||
EntityPatchRequest,
|
EntityPatchRequest,
|
||||||
EntityDownloadRequest,
|
EntityDownloadRequest,
|
||||||
|
EntityBlobsRequest,
|
||||||
|
EntityBlobsResponse,
|
||||||
|
EntityBlobsWireResponse,
|
||||||
} from '../types/entity';
|
} from '../types/entity';
|
||||||
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
import { useIntegrationStore } from '@KTXC/stores/integrationStore';
|
||||||
import { EntityObject } from '../models';
|
import { EntityObject } from '../models';
|
||||||
@@ -227,6 +230,36 @@ export const entityService = {
|
|||||||
download(request: EntityDownloadRequest): { transaction: string } {
|
download(request: EntityDownloadRequest): { transaction: string } {
|
||||||
return transceiveDownload<EntityDownloadRequest>('entity.download', request);
|
return transceiveDownload<EntityDownloadRequest>('entity.download', request);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch one or more message parts (attachments) inline for rendering.
|
||||||
|
*
|
||||||
|
* Returns JSON with base64-encoded bytes; each result is decoded into a Blob
|
||||||
|
* so callers can create object URLs for preview.
|
||||||
|
*/
|
||||||
|
async blobs(request: EntityBlobsRequest): Promise<EntityBlobsResponse> {
|
||||||
|
const wire = await transceivePost<EntityBlobsRequest, EntityBlobsWireResponse>(
|
||||||
|
'entity.blobs',
|
||||||
|
request,
|
||||||
|
);
|
||||||
|
return wire.map((result) => ({
|
||||||
|
source: result.source,
|
||||||
|
part: result.part,
|
||||||
|
mime: result.mime,
|
||||||
|
filename: result.filename,
|
||||||
|
blob: base64ToBlob(result.bytes, result.mime),
|
||||||
|
}));
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Decode base64 content into a Blob of the given MIME type. */
|
||||||
|
function base64ToBlob(base64: string, mime: string): Blob {
|
||||||
|
const binary = atob(base64);
|
||||||
|
const bytes = new Uint8Array(binary.length);
|
||||||
|
for (let i = 0; i < binary.length; i++) {
|
||||||
|
bytes[i] = binary.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new Blob([bytes], { type: mime });
|
||||||
|
}
|
||||||
|
|
||||||
export default entityService;
|
export default entityService;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { entityService } from '../services'
|
|||||||
import { EntityObject, MessageObject } from '../models'
|
import { EntityObject, MessageObject } from '../models'
|
||||||
import type {
|
import type {
|
||||||
EntityBlobSelector,
|
EntityBlobSelector,
|
||||||
|
EntityBlobsResponse,
|
||||||
EntityDownloadRequest,
|
EntityDownloadRequest,
|
||||||
EntityTransmitRequest,
|
EntityTransmitRequest,
|
||||||
EntityTransmitResponse,
|
EntityTransmitResponse,
|
||||||
@@ -508,6 +509,21 @@ export const useEntitiesStore = defineStore('mailEntitiesStore', () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch one or more message parts (attachments) inline as Blobs, for preview.
|
||||||
|
*/
|
||||||
|
async function blobs(
|
||||||
|
target: EntityIdentifier,
|
||||||
|
parts: EntityBlobSelector[],
|
||||||
|
): Promise<EntityBlobsResponse> {
|
||||||
|
try {
|
||||||
|
return await entityService.blobs({ target, parts })
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('[Mail Manager][Store] - Failed to fetch attachment blobs:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Return public API
|
// Return public API
|
||||||
return {
|
return {
|
||||||
// State (readonly)
|
// State (readonly)
|
||||||
@@ -530,5 +546,6 @@ export const useEntitiesStore = defineStore('mailEntitiesStore', () => {
|
|||||||
move,
|
move,
|
||||||
transmit,
|
transmit,
|
||||||
download,
|
download,
|
||||||
|
blobs,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
+9
-1
@@ -227,9 +227,17 @@ export interface EntityBlobsRequest {
|
|||||||
parts: EntityBlobSelector[];
|
parts: EntityBlobSelector[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EntityBlobsResult {
|
export interface EntityBlobsWireResult {
|
||||||
source: EntityIdentifier;
|
source: EntityIdentifier;
|
||||||
part: EntityBlobSelector;
|
part: EntityBlobSelector;
|
||||||
|
mime: string;
|
||||||
|
filename: string;
|
||||||
|
bytes: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EntityBlobsWireResponse extends Array<EntityBlobsWireResult> {}
|
||||||
|
|
||||||
|
export interface EntityBlobsResult extends Omit<EntityBlobsWireResult, 'bytes'> {
|
||||||
blob: Blob;
|
blob: Blob;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user