generated from Nodarx/template
feat: lots more improvements
Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
@@ -22,14 +22,11 @@ use KTXM\ProviderImap\Stores\ServiceStore;
|
||||
|
||||
/**
|
||||
* IMAP Mail Provider
|
||||
*
|
||||
* Registers IMAP as a mail provider and handles service lifecycle:
|
||||
* list / fetch / create / modify / destroy / discover / test.
|
||||
*/
|
||||
class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscoverInterface, ProviderServiceTestInterface
|
||||
{
|
||||
public const JSON_TYPE = ProviderBaseInterface::JSON_TYPE;
|
||||
|
||||
public const JSON_TYPE = ProviderBaseInterface::JSON_TYPE;
|
||||
protected const PROVIDER_IDENTIFIER = 'imap';
|
||||
protected const PROVIDER_LABEL = 'IMAP Mail Provider';
|
||||
protected const PROVIDER_DESCRIPTION = 'Provides mail services via the IMAP protocol';
|
||||
@@ -49,8 +46,6 @@ class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscove
|
||||
private readonly ServiceStore $serviceStore,
|
||||
) {}
|
||||
|
||||
// ── ProviderBaseInterface ─────────────────────────────────────────────────
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return [
|
||||
@@ -101,23 +96,24 @@ class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscove
|
||||
return $this->providerAbilities;
|
||||
}
|
||||
|
||||
// ── ProviderServiceMutateInterface ────────────────────────────────────────
|
||||
|
||||
public function serviceList(string $tenantId, string $userId, array $filter = []): array
|
||||
{
|
||||
$list = $this->serviceStore->list($tenantId, $userId, $filter);
|
||||
$result = [];
|
||||
foreach ($list as $entry) {
|
||||
$service = new Service();
|
||||
$service->fromStore($entry);
|
||||
$result[$service->identifier()] = $service;
|
||||
foreach ($list as $serviceData) {
|
||||
$serviceInstance = $this->serviceFresh()->fromStore($serviceData);
|
||||
$list[$serviceInstance->identifier()] = $serviceInstance;
|
||||
}
|
||||
return $result;
|
||||
return $list;
|
||||
}
|
||||
|
||||
public function serviceFetch(string $tenantId, string $userId, string|int $identifier): ?Service
|
||||
{
|
||||
return $this->serviceStore->fetch($tenantId, $userId, $identifier);
|
||||
$serviceData = $this->serviceStore->fetch($tenantId, $userId, $identifier);
|
||||
if ($serviceData === null) {
|
||||
return null;
|
||||
}
|
||||
$serviceInstance = $this->serviceFresh()->fromStore($serviceData);
|
||||
return $serviceInstance;
|
||||
}
|
||||
|
||||
public function serviceFindByAddress(string $tenantId, string $userId, string $address): ?Service
|
||||
@@ -137,7 +133,7 @@ class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscove
|
||||
return $this->serviceStore->extant($tenantId, $userId, $identifiers);
|
||||
}
|
||||
|
||||
public function serviceFresh(): ResourceServiceMutateInterface
|
||||
public function serviceFresh(): Service
|
||||
{
|
||||
return new Service();
|
||||
}
|
||||
@@ -145,21 +141,21 @@ class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscove
|
||||
public function serviceCreate(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string
|
||||
{
|
||||
if (!($service instanceof Service)) {
|
||||
throw new \InvalidArgumentException('Service must be an instance of IMAP Service');
|
||||
throw new \InvalidArgumentException('Service must be instance of IMAP Service');
|
||||
}
|
||||
|
||||
$created = $this->serviceStore->create($tenantId, $userId, $service);
|
||||
return (string) $created->identifier();
|
||||
return (string) $created['sid'];
|
||||
}
|
||||
|
||||
public function serviceModify(string $tenantId, string $userId, ResourceServiceMutateInterface $service): string
|
||||
{
|
||||
if (!($service instanceof Service)) {
|
||||
throw new \InvalidArgumentException('Service must be an instance of IMAP Service');
|
||||
throw new \InvalidArgumentException('Service must be instance of IMAP Service');
|
||||
}
|
||||
|
||||
$updated = $this->serviceStore->modify($tenantId, $userId, $service);
|
||||
return (string) $updated->identifier();
|
||||
return (string) $updated['sid'];
|
||||
}
|
||||
|
||||
public function serviceDestroy(string $tenantId, string $userId, ResourceServiceMutateInterface $service): bool
|
||||
@@ -171,23 +167,19 @@ class Provider implements ProviderServiceMutateInterface, ProviderServiceDiscove
|
||||
return $this->serviceStore->delete($tenantId, $userId, $service->identifier());
|
||||
}
|
||||
|
||||
// ── ProviderServiceDiscoverInterface ──────────────────────────────────────
|
||||
|
||||
public function serviceDiscover(
|
||||
string $tenantId,
|
||||
string $userId,
|
||||
string $identity,
|
||||
string $tenantId,
|
||||
string $userId,
|
||||
string $identity,
|
||||
?string $location = null,
|
||||
?string $secret = null,
|
||||
): ?ResourceServiceLocationInterface {
|
||||
?string $secret = null
|
||||
): ResourceServiceLocationInterface|null {
|
||||
$discovery = new Discovery();
|
||||
// TODO: Make SSL verification configurable per-tenant
|
||||
$verifySSL = true;
|
||||
|
||||
return $discovery->discover($identity, $location, $secret, $verifySSL);
|
||||
}
|
||||
|
||||
// ── ProviderServiceTestInterface ──────────────────────────────────────────
|
||||
|
||||
public function serviceTest(ServiceBaseInterface $service, array $options = []): array
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
@@ -168,7 +168,7 @@ class Service implements ServiceBaseInterface, ServiceMutableInterface, ServiceC
|
||||
], fn($v) => $v !== null);
|
||||
}
|
||||
|
||||
public function jsonDeserialize(array|string $data): static
|
||||
public function jsonDeserialize(array|string $data, bool $delta = false): static
|
||||
{
|
||||
if (is_string($data)) {
|
||||
$data = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
@@ -26,45 +26,39 @@ class ServiceStore
|
||||
|
||||
public function __construct(
|
||||
protected readonly DataStore $dataStore,
|
||||
protected readonly Crypto $crypto,
|
||||
protected readonly Crypto $crypto,
|
||||
) {}
|
||||
|
||||
// ── List ─────────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* List services for a tenant+user, optionally filtered to specific IDs.
|
||||
*
|
||||
* @param string[]|null $filter Service IDs to restrict results to
|
||||
* @return array<string, array> Keyed by service ID
|
||||
* List services for a tenant and user, optionally filtered by service IDs
|
||||
*/
|
||||
public function list(string $tenantId, string $userId, ?array $filter = null): array
|
||||
{
|
||||
$condition = ['tid' => $tenantId, 'uid' => $userId];
|
||||
$filterCondition = [
|
||||
'tid' => $tenantId,
|
||||
'uid' => $userId,
|
||||
];
|
||||
|
||||
if ($filter !== null && !empty($filter)) {
|
||||
$condition['sid'] = ['$in' => $filter];
|
||||
$filterCondition['sid'] = ['$in' => $filter];
|
||||
}
|
||||
|
||||
$cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find($condition);
|
||||
$cursor = $this->dataStore->selectCollection(self::COLLECTION_NAME)->find($filterCondition);
|
||||
|
||||
$list = [];
|
||||
foreach ($cursor as $entry) {
|
||||
|
||||
if (isset($entry['identity']['secret'])) {
|
||||
$entry['identity']['secret'] = $this->crypto->decrypt($entry['identity']['secret']);
|
||||
}
|
||||
|
||||
$list[$entry['sid']] = $entry;
|
||||
}
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
// ── Extant ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Check which of the supplied service IDs exist for the given tenant/user.
|
||||
*
|
||||
* @param string[]|int[] $identifiers
|
||||
* @return array<string, bool>
|
||||
* Check existence of services by IDs for a tenant and user
|
||||
*/
|
||||
public function extant(string $tenantId, string $userId, array $identifiers): array
|
||||
{
|
||||
@@ -76,27 +70,29 @@ class ServiceStore
|
||||
[
|
||||
'tid' => $tenantId,
|
||||
'uid' => $userId,
|
||||
'sid' => ['$in' => array_map('strval', $identifiers)],
|
||||
'sid' => ['$in' => array_map('strval', $identifiers)]
|
||||
],
|
||||
['projection' => ['sid' => 1]],
|
||||
['projection' => ['sid' => 1]]
|
||||
);
|
||||
|
||||
$existing = [];
|
||||
foreach ($cursor as $doc) {
|
||||
$existing[] = $doc['sid'];
|
||||
$existingIds = [];
|
||||
foreach ($cursor as $document) {
|
||||
$existingIds[] = $document['sid'];
|
||||
}
|
||||
|
||||
// Build result map: all identifiers default to false, existing ones set to true
|
||||
$result = [];
|
||||
foreach ($identifiers as $id) {
|
||||
$result[(string)$id] = in_array((string)$id, $existing, true);
|
||||
$result[(string) $id] = in_array((string) $id, $existingIds, true);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// ── Fetch ────────────────────────────────────────────────────────────────
|
||||
|
||||
public function fetch(string $tenantId, string $userId, string|int $serviceId): ?Service
|
||||
/**
|
||||
* Retrieve a single service by ID
|
||||
*/
|
||||
public function fetch(string $tenantId, string $userId, string|int $serviceId): ?array
|
||||
{
|
||||
$document = $this->dataStore->selectCollection(self::COLLECTION_NAME)->findOne([
|
||||
'tid' => $tenantId,
|
||||
@@ -112,57 +108,64 @@ class ServiceStore
|
||||
$document['identity']['secret'] = $this->crypto->decrypt($document['identity']['secret']);
|
||||
}
|
||||
|
||||
return (new Service())->fromStore($document);
|
||||
return $document;
|
||||
}
|
||||
|
||||
// ── Create ───────────────────────────────────────────────────────────────
|
||||
|
||||
public function create(string $tenantId, string $userId, Service $service): Service
|
||||
/**
|
||||
* Create a new service
|
||||
*/
|
||||
public function create(string $tenantId, string $userId, Service $service): array
|
||||
{
|
||||
$document = $service->toStore();
|
||||
|
||||
// prepare document for insertion
|
||||
$document['tid'] = $tenantId;
|
||||
$document['uid'] = $userId;
|
||||
$document['sid'] = UUID::v4();
|
||||
$document['createdOn'] = new \MongoDB\BSON\UTCDateTime();
|
||||
$document['createdOn'] = new \MongoDB\BSON\UTCDateTime();
|
||||
$document['modifiedOn'] = new \MongoDB\BSON\UTCDateTime();
|
||||
|
||||
if (isset($document['identity']['secret'])) {
|
||||
$document['identity']['secret'] = $this->crypto->encrypt($document['identity']['secret']);
|
||||
}
|
||||
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->insertOne($document);
|
||||
$result = $this->dataStore->selectCollection(self::COLLECTION_NAME)->insertOne($document);
|
||||
|
||||
return (new Service())->fromStore($document);
|
||||
return $document;
|
||||
}
|
||||
|
||||
// ── Modify ───────────────────────────────────────────────────────────────
|
||||
|
||||
public function modify(string $tenantId, string $userId, Service $service): Service
|
||||
/**
|
||||
* Modify an existing service
|
||||
*/
|
||||
public function modify(string $tenantId, string $userId, Service $service): array
|
||||
{
|
||||
$serviceId = $service->identifier();
|
||||
if (empty($serviceId)) {
|
||||
throw new \InvalidArgumentException('Service ID is required for update');
|
||||
}
|
||||
|
||||
// prepare document for modification
|
||||
$document = $service->toStore();
|
||||
$document['modifiedOn'] = new \MongoDB\BSON\UTCDateTime();
|
||||
|
||||
if (isset($document['identity']['secret'])) {
|
||||
$document['identity']['secret'] = $this->crypto->encrypt($document['identity']['secret']);
|
||||
}
|
||||
|
||||
unset($document['sid'], $document['tid'], $document['uid'], $document['createdOn']);
|
||||
|
||||
$this->dataStore->selectCollection(self::COLLECTION_NAME)->updateOne(
|
||||
['tid' => $tenantId, 'uid' => $userId, 'sid' => (string)$serviceId],
|
||||
['$set' => $document],
|
||||
[
|
||||
'tid' => $tenantId,
|
||||
'uid' => $userId,
|
||||
'sid' => (string)$serviceId,
|
||||
],
|
||||
['$set' => $document]
|
||||
);
|
||||
|
||||
return (new Service())->fromStore($document);
|
||||
return $document;
|
||||
}
|
||||
|
||||
// ── Delete ───────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Delete a service
|
||||
*/
|
||||
public function delete(string $tenantId, string $userId, string|int $serviceId): bool
|
||||
{
|
||||
$result = $this->dataStore->selectCollection(self::COLLECTION_NAME)->deleteOne([
|
||||
@@ -173,4 +176,5 @@ class ServiceStore
|
||||
|
||||
return $result->getDeletedCount() > 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user