diff --git a/src/Blob/BlobSubscriber.php b/src/Blob/BlobSubscriber.php
new file mode 100644
index 0000000..47ce09f
--- /dev/null
+++ b/src/Blob/BlobSubscriber.php
@@ -0,0 +1,97 @@
+config = $config;
+ $this->translator = $translator;
+ $this->typesenseSync = $typesenseSync;
+ }
+
+ private function isForCabinet(FileData $fileData): bool
+ {
+ $bucket = $fileData->getBucket();
+ if ($bucket->getBucketID() !== $this->config->getBlobBucketId()) {
+ return false;
+ }
+ if ($fileData->getPrefix() !== $this->config->getBlobBucketPrefix()) {
+ return false;
+ }
+
+ return true;
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ return [
+ AddFileDataByPostSuccessEvent::class => 'onFileAdded',
+ ChangeFileDataByPatchSuccessEvent::class => 'onFileChanged',
+ DeleteFileDataByDeleteSuccessEvent::class => 'onFileRemoved',
+ ];
+ }
+
+ private function translateMetadata(FileData $fileData): array
+ {
+ $metadata = json_decode($fileData->getMetadata(), associative: true, flags: JSON_THROW_ON_ERROR);
+ $objectType = $metadata['objectType'];
+ $input = [
+ 'id' => $fileData->getIdentifier(),
+ 'fileSource' => $fileData->getBucket()->getBucketID(),
+ 'fileName' => $fileData->getFileName(),
+ 'metadata' => $metadata,
+ ];
+
+ return $this->translator->translateDocument($objectType, $input);
+ }
+
+ public function onFileAdded(AddFileDataByPostSuccessEvent $event)
+ {
+ $fileData = $event->getFileData();
+ if (!$this->isForCabinet($fileData)) {
+ return;
+ }
+
+ $translated = $this->translateMetadata($fileData);
+ $this->typesenseSync->upsertPartialFile($translated);
+ }
+
+ public function onFileChanged(ChangeFileDataByPatchSuccessEvent $event)
+ {
+ $fileData = $event->getFileData();
+ if (!$this->isForCabinet($fileData)) {
+ return;
+ }
+
+ $translated = $this->translateMetadata($fileData);
+ $this->typesenseSync->upsertPartialFile($translated);
+ }
+
+ public function onFileRemoved(DeleteFileDataByDeleteSuccessEvent $event)
+ {
+ $fileData = $event->getFileData();
+ if (!$this->isForCabinet($fileData)) {
+ return;
+ }
+
+ $translated = $this->translateMetadata($fileData);
+ $this->typesenseSync->removePartialFile($translated);
+ }
+}
diff --git a/src/Command/DeleteFileCommand.php b/src/Command/DeleteFileCommand.php
new file mode 100644
index 0000000..99d6581
--- /dev/null
+++ b/src/Command/DeleteFileCommand.php
@@ -0,0 +1,49 @@
+blobService = $blobService;
+ }
+
+ protected function configure(): void
+ {
+ $this->setName('dbp:relay:cabinet:delete-file');
+ $this->setDescription('Delete a file from the cabinet blob bucket');
+ $this->addArgument('id', InputArgument::REQUIRED, 'The ID of the file');
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $fileId = $input->getArgument('id');
+
+ try {
+ $this->blobService->deleteFile($fileId);
+ $output->writeln("File deleted successfully: $fileId");
+
+ return Command::SUCCESS;
+ } catch (BlobApiError $e) {
+ $output->writeln('Error deleting file: '.$e->getMessage().' ');
+ $output->writeln(print_r($e->getErrorId(), true));
+ $output->writeln(print_r($e->getErrorDetails(), true));
+
+ return Command::FAILURE;
+ }
+ }
+}
diff --git a/src/Command/UploadFileCommand.php b/src/Command/UploadFileCommand.php
index 9bc2dc4..4314478 100644
--- a/src/Command/UploadFileCommand.php
+++ b/src/Command/UploadFileCommand.php
@@ -32,6 +32,8 @@ protected function configure(): void
$this->addArgument('filepath', InputArgument::REQUIRED, 'The path to the file to upload');
$this->addOption('type', 't', InputOption::VALUE_OPTIONAL, 'The type of the file');
$this->addOption('metadata', 'm', InputOption::VALUE_OPTIONAL, 'The metadata of the file');
+ $this->addOption('filename', 'f', InputOption::VALUE_REQUIRED,
+ 'The filename, defaults to the filename of the given filepath');
}
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -48,7 +50,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int
return Command::FAILURE;
}
- $filename = basename($filepath);
+ $filename = $input->getOption('filename') ?? basename($filepath);
$payload = file_get_contents($filepath);
if ($payload === false) {
diff --git a/src/EventSubscriber/BlobAddFileSuccessSubscriber.php b/src/EventSubscriber/BlobAddFileSuccessSubscriber.php
deleted file mode 100644
index 320d8fa..0000000
--- a/src/EventSubscriber/BlobAddFileSuccessSubscriber.php
+++ /dev/null
@@ -1,27 +0,0 @@
- 'onPost',
- ];
- }
-
- public function onPost(AddFileDataByPostSuccessEvent $event)
- {
- // dump($event);
- }
-}
diff --git a/src/Resources/config/services.yaml b/src/Resources/config/services.yaml
index 1137086..73b9f5c 100644
--- a/src/Resources/config/services.yaml
+++ b/src/Resources/config/services.yaml
@@ -48,6 +48,10 @@ services:
autowire: true
autoconfigure: true
+ Dbp\Relay\CabinetBundle\Command\DeleteFileCommand:
+ autowire: true
+ autoconfigure: true
+
Dbp\Relay\CabinetBundle\Authorization\AuthorizationService:
autowire: true
autoconfigure: true
@@ -65,8 +69,8 @@ services:
autowire: true
autoconfigure: true
- Dbp\Relay\CabinetBundle\EventSubscriber\:
- resource: '../../EventSubscriber'
+ Dbp\Relay\CabinetBundle\Blob\:
+ resource: '../../Blob'
autowire: true
autoconfigure: true
diff --git a/src/Service/BlobService.php b/src/Service/BlobService.php
index 8f654c2..48ac25b 100644
--- a/src/Service/BlobService.php
+++ b/src/Service/BlobService.php
@@ -49,6 +49,13 @@ public function uploadFile(string $filename, string $payload, ?string $type = nu
return $blobApi->uploadFile($this->config->getBlobBucketPrefix(), $filename, $payload, $metadata ?? '', $type ?? '');
}
+ public function deleteFile(string $id): void
+ {
+ $blobApi = $this->getInternalBlobApi();
+
+ $blobApi->deleteFileByIdentifier($id);
+ }
+
public function getSignatureForGivenRequest(Request $request): Response
{
if (!$this->auth->isAuthenticated()) {
diff --git a/src/TypesenseClient/SearchIndex.php b/src/TypesenseClient/SearchIndex.php
index ebc905d..394d208 100644
--- a/src/TypesenseClient/SearchIndex.php
+++ b/src/TypesenseClient/SearchIndex.php
@@ -91,6 +91,30 @@ public function addDocumentsToCollection(string $collectionName, array $document
}
}
+ /**
+ * Returns all documents of type $type where $key matches $value.
+ */
+ public function findDocuments(string $collectionName, string $type, string $key, string $value): array
+ {
+ if (preg_match('/\s/', $value) || preg_match('/\s/', $type)) {
+ throw new \RuntimeException('no whitespace supported');
+ }
+ $filterBy = $key.':='.$value.' && @type := '.$type;
+ $searchResult = $this->getClient()->collections[$collectionName]->documents->search(['q' => '*', 'filter_by' => $filterBy]);
+
+ $documents = [];
+ foreach ($searchResult['hits'] as $hit) {
+ $documents[] = $hit['document'];
+ }
+
+ return $documents;
+ }
+
+ public function deleteDocument(string $collectionName, string $id): void
+ {
+ $this->getClient()->collections[$collectionName]->documents[$id]->delete();
+ }
+
public function updateAlias(string $collectionName): void
{
$aliasName = $this->getAliasName();
diff --git a/src/TypesenseSync/TypesenseSync.php b/src/TypesenseSync/TypesenseSync.php
index 0fe46ef..4b93478 100644
--- a/src/TypesenseSync/TypesenseSync.php
+++ b/src/TypesenseSync/TypesenseSync.php
@@ -149,6 +149,32 @@ private static function generateRandomPDFNames($count = 50): array
return $fileNames;
}
+ public function upsertPartialFile(array $partialFileDocument): void
+ {
+ $collectionName = $this->searchIndex->getAliasName();
+
+ $id = $partialFileDocument['base']['identNrObfuscated'];
+
+ // Find the matching person, and copy over the base info
+ $results = $this->searchIndex->findDocuments($collectionName, 'Person', 'base.identNrObfuscated', $id);
+ if ($results) {
+ $partialFileDocument['base'] = $results[0]['base'];
+ } else {
+ // FIXME: what if the person is missing? Just ignore the document until the next full sync, or poll later?
+ // atm the schema needs those fields, so just skip for now
+ return;
+ }
+
+ $this->searchIndex->addDocumentsToCollection($collectionName, [$partialFileDocument]);
+ }
+
+ public function removePartialFile(array $partialFileDocument): void
+ {
+ $collectionName = $this->searchIndex->getAliasName();
+ $id = $partialFileDocument['id'];
+ $this->searchIndex->deleteDocument($collectionName, $id);
+ }
+
public function addDummyDocuments(string $collectionName, array $personDocuments): void
{
$documents = [];