JMAP Registry API implementation - part 13

This commit is contained in:
mdecimus
2026-03-11 16:33:49 +01:00
parent a747239b61
commit 45af5efa4c
44 changed files with 2476 additions and 473 deletions

View File

@@ -8,16 +8,25 @@ use crate::task_manager::TaskResult;
use common::Server;
use email::{message::metadata::MessageMetadata, sieve::SieveScript};
use groupware::file::FileNode;
use registry::schema::structs::TaskDestroyAccount;
use registry::{
schema::{
prelude::{ObjectType, Property},
structs::{ArchivedItem, TaskDestroyAccount},
},
types::EnumImpl,
};
use store::{
SerializeInfallible, ValueKey,
registry::RegistryQuery,
search::SearchQuery,
write::{BatchBuilder, BlobLink, BlobOp, SearchIndex, ValueClass},
write::{BatchBuilder, BlobLink, BlobOp, RegistryClass, SearchIndex, ValueClass},
};
use trc::AddContext;
use types::{
blob_hash::BlobHash,
collection::Collection,
field::{EmailField, Field},
id::Id,
};
pub(crate) trait DestroyAccountTask: Sync + Send {
@@ -43,7 +52,87 @@ impl DestroyAccountTask for Server {
async fn destroy_account(server: &Server, task: &TaskDestroyAccount) -> trc::Result<TaskResult> {
let account_id = task.account_id.document_id();
let todo = "destroy spam samples, undelete, registry objects, etc.";
// Destroy public keys and masked emails
for object in [ObjectType::PublicKey, ObjectType::MaskedEmail] {
let mut batch = BatchBuilder::new();
let ids = server
.registry()
.query::<Vec<Id>>(RegistryQuery::new(object).with_account(account_id))
.await?;
let object_id = object.to_id();
for id in ids {
batch
.clear(ValueClass::Registry(RegistryClass::Item {
object_id,
item_id: id.id(),
}))
.clear(ValueClass::Registry(RegistryClass::IndexId {
object_id,
item_id: id.id(),
}))
.clear(ValueClass::Registry(RegistryClass::Index {
index_id: Property::AccountId as u16,
object_id,
item_id: id.id(),
key: (account_id as u64).serialize(),
}))
.clear(ValueClass::Registry(RegistryClass::Reference {
to_object_id: ObjectType::Account as u16,
to_item_id: account_id as u64,
from_object_id: object_id,
from_item_id: id.id(),
}));
}
if !batch.is_empty() {
server.store().write(batch.build_all()).await?;
}
}
// Remove archived items
let mut batch = BatchBuilder::new();
let ids = server
.registry()
.query::<Vec<Id>>(RegistryQuery::new(ObjectType::ArchivedItem).with_account(account_id))
.await?;
for id in ids {
let object_id = ObjectType::ArchivedItem.to_id();
let item_id = id.id();
if let Some(item) = server
.store()
.get_value::<ArchivedItem>(ValueKey::from(ValueClass::Registry(RegistryClass::Item {
object_id,
item_id,
})))
.await?
{
let until = item.archived_until().timestamp() as u64;
let blob_hash = item.into_blob_id().hash;
batch
.with_account_id(account_id)
.clear(BlobOp::Link {
hash: blob_hash,
to: BlobLink::Temporary { until },
})
.clear(ValueClass::Registry(RegistryClass::Index {
index_id: Property::AccountId.to_id(),
object_id,
item_id,
key: (account_id as u64).serialize(),
}))
.clear(ValueClass::Registry(RegistryClass::Item {
object_id,
item_id,
}));
}
}
if !batch.is_empty() {
server.store().write(batch.build_all()).await?;
}
// Remove search index
for index in [

View File

@@ -26,22 +26,27 @@ use groupware::{
contact::{AddressBook, ContactCard},
file::FileNode,
};
use registry::schema::{
enums::{TaskAccountMaintenanceType, TaskStoreMaintenanceType},
prelude::ObjectType,
structs::{Task, TaskAccountMaintenance, TaskStatus, TaskStoreMaintenance},
use registry::{
schema::{
enums::{TaskAccountMaintenanceType, TaskStoreMaintenanceType},
prelude::{Object, ObjectInner, ObjectType, Property},
structs::{Task, TaskAccountMaintenance, TaskStatus, TaskStoreMaintenance},
},
types::EnumImpl,
};
use smtp::reporting::index::ExternalReportIndex;
use store::{
Serialize, ValueKey,
rand::{self, Rng},
registry::RegistryQuery,
registry::{RegistryFilter, RegistryQuery},
roaring::RoaringBitmap,
write::{AlignedBytes, Archive, Archiver, BatchBuilder, ValueClass, now},
write::{AlignedBytes, Archive, Archiver, BatchBuilder, RegistryClass, ValueClass, now},
};
use trc::{AddContext, StoreEvent};
use types::{
collection::Collection,
field::{EmailField, MailboxField},
id::Id,
};
pub(crate) trait MaintenanceTask: Sync + Send {
@@ -123,9 +128,55 @@ async fn store_maintenance(
reindex_telemetry(server).await?;
}
TaskStoreMaintenanceType::PurgeData => {
let todo = "make sure all store types are purged, in memory, metrics, tracing, etc";
let todo =
"make sure spam samples with their indexes and undelete items are purged as well";
// Delete expired external reports
let now = now();
let mut batch = BatchBuilder::new();
for object in [
ObjectType::DmarcExternalReport,
ObjectType::TlsExternalReport,
ObjectType::ArfExternalReport,
] {
let ids = server
.registry()
.query::<Vec<Id>>(RegistryQuery::new(object).filter(RegistryFilter::less_than(
Property::ExpiresAt,
now,
false,
)))
.await?;
let object_id = object.to_id();
for id in ids {
let item_id = id.id();
if let Some(report) = server
.store()
.get_value::<Object>(ValueKey::from(ValueClass::Registry(
RegistryClass::Item { object_id, item_id },
)))
.await?
{
match &report.inner {
ObjectInner::DmarcExternalReport(report) => {
report.write_ops(&mut batch, item_id, false);
}
ObjectInner::TlsExternalReport(report) => {
report.write_ops(&mut batch, item_id, false);
}
ObjectInner::ArfExternalReport(report) => {
report.write_ops(&mut batch, item_id, false);
}
_ => {}
}
if batch.is_large_batch() {
server.store().write(batch.build_all()).await?;
batch = BatchBuilder::new();
}
}
}
}
if !batch.is_empty() {
server.store().write(batch.build_all()).await?;
}
let started = Instant::now();