2 Commits

Author SHA1 Message Date
Sebastian f901f13953 feat: listen to user life cycle events
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-08-09 22:36:41 -04:00
Sebastian f39dd75ae5 fix: folder deletion while in trash
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
2026-08-07 20:45:29 -04:00
4 changed files with 73 additions and 9 deletions
+27
View File
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace KTXM\ProviderJmapc\Listeners;
use KTXC\User\Event\UserCreatedEvent;
use KTXC\User\Event\UserDeletingEvent;
use KTXM\ProviderJmapc\Stores\ServiceStore;
final class UserEventListener
{
public function __construct(
private readonly ServiceStore $serviceStore,
) {
}
public function onUserCreated(UserCreatedEvent $event): void
{
// TODO: implement provisioning configuration
}
public function onUserDeleting(UserDeletingEvent $event): void
{
$this->serviceStore->deleteByUser($event->tenantIdentifier(), $event->userIdentifier());
}
}
+9
View File
@@ -10,9 +10,14 @@ declare(strict_types=1);
namespace KTXM\ProviderJmapc;
use KTXC\Resource\ProviderManager;
use KTXC\User\Event\UserCreatedEvent;
use KTXC\User\Event\UserDeletingEvent;
use KTXF\Event\DeliveryMode;
use KTXF\Event\EventListenerRegistrarInterface;
use KTXF\Module\ModuleBrowserInterface;
use KTXF\Module\ModuleInstanceAbstract;
use KTXF\Resource\Provider\ProviderInterface;
use KTXM\ProviderJmapc\Listeners\UserEventListener;
use KTXM\ProviderJmapc\Providers\Mail\Provider as MailProvider;
use KTXM\ProviderJmapc\Providers\Chrono\Provider as ChronoProvider;
use KTXM\ProviderJmapc\Providers\People\Provider as PeopleProvider;
@@ -27,6 +32,7 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
{
public function __construct(
private readonly ProviderManager $providerManager,
private readonly EventListenerRegistrarInterface $events,
) {}
public function handle(): string
@@ -67,6 +73,9 @@ class Module extends ModuleInstanceAbstract implements ModuleBrowserInterface
public function boot(): void
{
// Register listeners
$this->events->listen($this->handle(), UserCreatedEvent::class, UserEventListener::class, 'onUserCreated', DeliveryMode::Deferred);
$this->events->listen($this->handle(), UserDeletingEvent::class, UserEventListener::class, 'onUserDeleting', DeliveryMode::Deferred);
// Register JMAP providers - all three share the same service store
$this->providerManager->register(ProviderInterface::TYPE_MAIL, 'jmap', MailProvider::class);
//$this->providerManager->register(ProviderInterface::TYPE_CHRONO, 'jmap', ChronoProvider::class);
+26 -9
View File
@@ -484,17 +484,34 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
}
}
// we need to determine if the folder being deleted is already in the trash
if (str_starts_with((string) $target->collection(), (string) $deleteTargetIdentifier?->collection())) {
// if so, we should hard delete instead of moving to avoid duplicates in the trash
$deleteMode = 'hard';
if ($deleteMode === 'soft') {
$targetMailbox = $this->mailService->collectionFetch((string) $target->collection());
if ($targetMailbox === null) {
throw new \RuntimeException('Collection not found for delete operation');
}
$result = match ($deleteMode) {
'soft' => $this->collectionMove($deleteTargetIdentifier, $target),
'hard' => $this->mailService->collectionDestroy($target->collection(), $force),
};
return $result;
$targetIdentifier = (string) $target->collection();
$trashIdentifier = (string) $deleteTargetIdentifier->collection();
$parentIdentifier = isset($targetMailbox['parentId'])
? (string) $targetMailbox['parentId']
: null;
// JMAP mailbox IDs are opaque, so containment must be determined
// from parentId rather than by comparing ID prefixes.
if ($targetIdentifier === $trashIdentifier || $parentIdentifier === $trashIdentifier) {
$deleteMode = 'hard';
}
}
if ($deleteMode === 'soft') {
return $this->collectionMove($deleteTargetIdentifier, $target);
}
if ($this->mailService->collectionDestroy((string) $target->collection(), $force) === null) {
throw new \RuntimeException("Failed to delete collection: {$target->collection()}");
}
return true;
}
public function collectionMove(CollectionIdentifier $target, CollectionIdentifier $source): CollectionBaseInterface
+11
View File
@@ -176,4 +176,15 @@ class ServiceStore
return $result->getDeletedCount() > 0;
}
/**
* Delete every service owned by a user.
*/
public function deleteByUser(string $tenantId, string $userId): void
{
$this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteMany([
'tid' => $tenantId,
'uid' => $userId,
]);
}
}