generated from Nodarx/template
225 lines
6.7 KiB
PHP
225 lines
6.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* SPDX-FileCopyrightText: Sebastian Krupinski <krupinski01@gmail.com>
|
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
*/
|
|
|
|
namespace KTXM\ProviderImap\Stores;
|
|
|
|
use KTXC\Db\DataStore;
|
|
|
|
/**
|
|
* MongoDB cache for IMAP messages and mailboxes.
|
|
*
|
|
* Collections:
|
|
* provider_imap_mail_messages — one document per (sid, mailbox, uid)
|
|
* provider_imap_mail_mailboxes — one document per (sid, name)
|
|
*
|
|
* Documents are stored **pre-formatted** in the same internal shape used by
|
|
* EntityResource / MessageProperties and CollectionResource / CollectionProperties
|
|
* so the read path does zero IMAP parsing.
|
|
*/
|
|
class MessageStore
|
|
{
|
|
protected const MESSAGES_COLLECTION = 'provider_imap_mail_messages';
|
|
protected const MAILBOXES_COLLECTION = 'provider_imap_mail_mailboxes';
|
|
|
|
public function __construct(
|
|
protected readonly DataStore $store,
|
|
) {
|
|
$this->ensureIndexes();
|
|
}
|
|
|
|
// ── Index creation ───────────────────────────────────────────────────────
|
|
|
|
private function ensureIndexes(): void
|
|
{
|
|
$messages = $this->store->selectCollection(self::MESSAGES_COLLECTION);
|
|
$mailboxes = $this->store->selectCollection(self::MAILBOXES_COLLECTION);
|
|
|
|
$messages->createIndex(['sid' => 1, 'mailbox' => 1, 'uid' => 1], ['unique' => true]);
|
|
$mailboxes->createIndex(['sid' => 1, 'name' => 1], ['unique' => true]);
|
|
}
|
|
|
|
// ── Messages ─────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Insert or replace a cached message document.
|
|
*
|
|
* @param array $data Result of EntityResource::toStore() — must include
|
|
* sid, mailbox, uid at the top level.
|
|
*/
|
|
public function upsertMessage(array $data): void
|
|
{
|
|
$filter = [
|
|
'sid' => $data['sid'],
|
|
'mailbox' => $data['mailbox'],
|
|
'uid' => (int)$data['uid'],
|
|
];
|
|
$data['syncedAt'] = (new \DateTime())->format(\DateTimeInterface::ATOM);
|
|
|
|
$this->store->selectCollection(self::MESSAGES_COLLECTION)->updateOne(
|
|
$filter,
|
|
['$set' => $data],
|
|
['upsert' => true],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single cached message by service ID, mailbox and UID.
|
|
*/
|
|
public function fetchMessage(string $serviceId, string $mailbox, int $uid): ?array
|
|
{
|
|
$doc = $this->store->selectCollection(self::MESSAGES_COLLECTION)->findOne([
|
|
'sid' => $serviceId,
|
|
'mailbox' => $mailbox,
|
|
'uid' => $uid,
|
|
]);
|
|
|
|
return $doc ? (array)$doc : null;
|
|
}
|
|
|
|
/**
|
|
* Return all cached UIDs for a mailbox/service combination.
|
|
*
|
|
* @return int[]
|
|
*/
|
|
public function listUids(string $serviceId, string $mailbox): array
|
|
{
|
|
$cursor = $this->store->selectCollection(self::MESSAGES_COLLECTION)->find(
|
|
['sid' => $serviceId, 'mailbox' => $mailbox],
|
|
['projection' => ['uid' => 1]],
|
|
);
|
|
|
|
$uids = [];
|
|
foreach ($cursor as $doc) {
|
|
$uids[] = (int)$doc['uid'];
|
|
}
|
|
|
|
return $uids;
|
|
}
|
|
|
|
/**
|
|
* Fetch multiple cached messages by UID.
|
|
*
|
|
* @param int[] $uids
|
|
* @return array[]
|
|
*/
|
|
public function fetchMessages(string $serviceId, string $mailbox, array $uids): array
|
|
{
|
|
$cursor = $this->store->selectCollection(self::MESSAGES_COLLECTION)->find([
|
|
'sid' => $serviceId,
|
|
'mailbox' => $mailbox,
|
|
'uid' => ['$in' => $uids],
|
|
]);
|
|
|
|
$result = [];
|
|
foreach ($cursor as $doc) {
|
|
$doc = (array)$doc;
|
|
$result[$doc['uid']] = $doc;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Delete a cached message entry by UID.
|
|
*/
|
|
public function deleteMessage(string $serviceId, string $mailbox, int $uid): void
|
|
{
|
|
$this->store->selectCollection(self::MESSAGES_COLLECTION)->deleteOne([
|
|
'sid' => $serviceId,
|
|
'mailbox' => $mailbox,
|
|
'uid' => $uid,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Delete multiple cached message entries.
|
|
*
|
|
* @param int[] $uids
|
|
*/
|
|
public function deleteMessages(string $serviceId, string $mailbox, array $uids): void
|
|
{
|
|
if (empty($uids)) {
|
|
return;
|
|
}
|
|
|
|
$this->store->selectCollection(self::MESSAGES_COLLECTION)->deleteMany([
|
|
'sid' => $serviceId,
|
|
'mailbox' => $mailbox,
|
|
'uid' => ['$in' => $uids],
|
|
]);
|
|
}
|
|
|
|
// ── Mailboxes ────────────────────────────────────────────────────────────
|
|
|
|
/**
|
|
* Insert or replace a cached mailbox document.
|
|
*
|
|
* @param array $data Result of CollectionResource::toStore() — must include
|
|
* sid and name at the top level.
|
|
*/
|
|
public function upsertMailbox(array $data): void
|
|
{
|
|
$filter = [
|
|
'sid' => $data['sid'],
|
|
'name' => $data['name'],
|
|
];
|
|
$data['syncedAt'] = (new \DateTime())->format(\DateTimeInterface::ATOM);
|
|
|
|
$this->store->selectCollection(self::MAILBOXES_COLLECTION)->updateOne(
|
|
$filter,
|
|
['$set' => $data],
|
|
['upsert' => true],
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieve a cached mailbox by service ID and name.
|
|
*/
|
|
public function fetchMailbox(string $serviceId, string $name): ?array
|
|
{
|
|
$doc = $this->store->selectCollection(self::MAILBOXES_COLLECTION)->findOne([
|
|
'sid' => $serviceId,
|
|
'name' => $name,
|
|
]);
|
|
|
|
return $doc ? (array)$doc : null;
|
|
}
|
|
|
|
/**
|
|
* List all cached mailboxes for a service.
|
|
*
|
|
* @return array[]
|
|
*/
|
|
public function listMailboxes(string $serviceId): array
|
|
{
|
|
$cursor = $this->store->selectCollection(self::MAILBOXES_COLLECTION)->find(
|
|
['sid' => $serviceId],
|
|
);
|
|
|
|
$result = [];
|
|
foreach ($cursor as $doc) {
|
|
$doc = (array)$doc;
|
|
$result[$doc['name']] = $doc;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Delete a cached mailbox entry by service ID and name.
|
|
*/
|
|
public function deleteMailbox(string $serviceId, string $name): void
|
|
{
|
|
$this->store->selectCollection(self::MAILBOXES_COLLECTION)->deleteOne([
|
|
'sid' => $serviceId,
|
|
'name' => $name,
|
|
]);
|
|
}
|
|
}
|